/***************************************************
  Arduino TFT graphics library targeted at ESP8266
  and ESP32 based boards.

  This is a standalone library that contains the
  hardware driver, the graphics functions and the
  proportional fonts.

  The larger fonts are Run Length Encoded to reduce their
  size.

  Created by Bodmer 2/12/16
  Bodmer: Added RPi 16 bit display support
 ****************************************************/

#include "In_eSPI.h"

#if defined(ESP32)
#if !defined(ESP32_PARALLEL)
#ifdef USE_HSPI_PORT
SPIClass spi = SPIClass(HSPI);
#else  // use default VSPI port
SPIClass spi = SPIClass(VSPI);
#endif
#endif
#else  // ESP8266
SPIClass spi = SPIClass();
#endif

// SUPPORT_TRANSACTIONS is mandatory for ESP32 so the hal mutex is toggled
#if defined(ESP32) && !defined(SUPPORT_TRANSACTIONS)
#define SUPPORT_TRANSACTIONS
#endif

// If it is a 16bit serial display we must transfer 16 bits every time
#ifdef RPI_ILI9486_DRIVER
#define CMD_BITS 16 - 1
#else
#define CMD_BITS 8 - 1
#endif

// Fast block write prototype
void writeBlock(uint16_t color, uint32_t repeat);

// Byte read prototype
uint8_t readByte(void);

// GPIO parallel input/output control
void busDir(uint32_t mask, uint8_t mode);

inline void TFT_eSPI::spi_begin(void) {
#if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS) && \
    !defined(ESP32_PARALLEL)
    if (locked) {
        locked = false;
        spi.beginTransaction(
            SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
        CS_L;
    }
#else
    CS_L;
#endif
#ifdef ESP8266
    SPI1U = SPI1U_WRITE;
#endif
}

inline void TFT_eSPI::spi_end(void) {
#if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS) && \
    !defined(ESP32_PARALLEL)
    if (!inTransaction) {
        if (!locked) {
            locked = true;
            CS_H;
            spi.endTransaction();
        }
    }
#ifdef ESP8266
    SPI1U = SPI1U_READ;
#endif
#else
    if (!inTransaction) CS_H;
#endif
}

inline void TFT_eSPI::spi_begin_read(void) {
#if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS) && \
    !defined(ESP32_PARALLEL)
    if (locked) {
        locked = false;
        spi.beginTransaction(
            SPISettings(SPI_READ_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
        CS_L;
    }
#else
#if !defined(ESP32_PARALLEL)
    spi.setFrequency(SPI_READ_FREQUENCY);
#endif
    CS_L;
#endif
#ifdef ESP8266
    SPI1U = SPI1U_READ;
#endif
}

inline void TFT_eSPI::spi_end_read(void) {
#if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS) && \
    !defined(ESP32_PARALLEL)
    if (!inTransaction) {
        if (!locked) {
            locked = true;
            CS_H;
            spi.endTransaction();
        }
    }
#else
#if !defined(ESP32_PARALLEL)
    spi.setFrequency(SPI_FREQUENCY);
#endif
    if (!inTransaction) CS_H;
#endif
#ifdef ESP8266
    SPI1U = SPI1U_WRITE;
#endif
}

#if defined(TOUCH_CS) && \
    defined(SPI_TOUCH_FREQUENCY)  // && !defined(ESP32_PARALLEL)

inline void TFT_eSPI::spi_begin_touch(void) {
    CS_H;  // Just in case it has been left low

#if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS)
    if (locked) {
        locked = false;
        spi.beginTransaction(
            SPISettings(SPI_TOUCH_FREQUENCY, MSBFIRST, SPI_MODE0));
    }
#else
    spi.setFrequency(SPI_TOUCH_FREQUENCY);
#endif

#ifdef ESP8266
    SPI1U = SPI1U_READ;
#endif

    T_CS_L;
}

inline void TFT_eSPI::spi_end_touch(void) {
    T_CS_H;

#if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS)
    if (!inTransaction) {
        if (!locked) {
            locked = true;
            spi.endTransaction();
        }
    }
#else
    spi.setFrequency(SPI_FREQUENCY);
#endif

#ifdef ESP8266
    SPI1U = SPI1U_WRITE;
#endif
}

#endif

/***************************************************************************************
** Function name:           TFT_eSPI
** Description:             Constructor , we must use hardware SPI pins
***************************************************************************************/
TFT_eSPI::TFT_eSPI(int16_t w, int16_t h) {
// The control pins are deliberately set to the inactive state (CS high) as
// setup() might call and initialise other SPI peripherals which would could
// cause conflicts if CS is floating or undefined.
#ifdef TFT_CS
    digitalWrite(TFT_CS, HIGH);  // Chip select high (inactive)
    pinMode(TFT_CS, OUTPUT);
#endif

// Configure chip select for touchscreen controller if present
#ifdef TOUCH_CS
    digitalWrite(TOUCH_CS, HIGH);  // Chip select high (inactive)
    pinMode(TOUCH_CS, OUTPUT);
#endif

#ifdef TFT_WR
    digitalWrite(TFT_WR, HIGH);  // Set write strobe high (inactive)
    pinMode(TFT_WR, OUTPUT);
#endif

#ifdef TFT_DC
    digitalWrite(TFT_DC, HIGH);  // Data/Command high = data mode
    pinMode(TFT_DC, OUTPUT);
#endif

#ifdef TFT_RST
    if (TFT_RST >= 0) {
        digitalWrite(
            TFT_RST,
            HIGH);  // Set high, do not share pin with another SPI device
        pinMode(TFT_RST, OUTPUT);
    }
#endif

#ifdef ESP32_PARALLEL

    // Create a bit set lookup table for data bus - wastes 1kbyte of RAM but
    // speeds things up dramatically
    for (int32_t c = 0; c < 256; c++) {
        xset_mask[c] = 0;
        if (c & 0x01) xset_mask[c] |= (1 << TFT_D0);
        if (c & 0x02) xset_mask[c] |= (1 << TFT_D1);
        if (c & 0x04) xset_mask[c] |= (1 << TFT_D2);
        if (c & 0x08) xset_mask[c] |= (1 << TFT_D3);
        if (c & 0x10) xset_mask[c] |= (1 << TFT_D4);
        if (c & 0x20) xset_mask[c] |= (1 << TFT_D5);
        if (c & 0x40) xset_mask[c] |= (1 << TFT_D6);
        if (c & 0x80) xset_mask[c] |= (1 << TFT_D7);
    }

    // Make sure read is high before we set the bus to output
    digitalWrite(TFT_RD, HIGH);
    pinMode(TFT_RD, OUTPUT);

    GPIO.out_w1ts = set_mask(255);  // Set data bus to 0xFF

    // Set TFT data bus lines to output
    busDir(dir_mask, OUTPUT);

#endif

    _init_width = _width =
        w;  // Set by specific xxxxx_Defines.h file or by users sketch
    _init_height = _height =
        h;  // Set by specific xxxxx_Defines.h file or by users sketch
    rotation = 0;
    cursor_y = cursor_x = 0;
    textfont            = 1;
    textsize            = 1;
    textcolor = bitmap_fg = 0xFFFF;    // White
    textbgcolor = bitmap_bg = 0x0000;  // Black
    padX                    = 0;       // No padding
    isDigits                = false;   // No bounding box adjustment
    textwrapX = true;   // Wrap text at end of line when using print stream
    textwrapY = false;  // Wrap text at bottom of screen when using print stream
    textdatum = TL_DATUM;  // Top Left text alignment is default
    fontsloaded = 0;

    _swapBytes = false;  // Do not swap colour bytes by default

    locked        = true;  // ESP32 transaction mutex lock flags
    inTransaction = false;

    _booted = true;
    _cp437  = true;
    _utf8   = true;

    addr_row = 0xFFFF;
    addr_col = 0xFFFF;

    _xpivot = 0;
    _ypivot = 0;

#ifdef LOAD_GLCD
    fontsloaded = 0x0002;  // Bit 1 set
#endif

#ifdef LOAD_FONT2
    fontsloaded |= 0x0004;  // Bit 2 set
#endif

#ifdef LOAD_FONT4
    fontsloaded |= 0x0010;  // Bit 4 set
#endif

#ifdef LOAD_FONT6
    fontsloaded |= 0x0040;  // Bit 6 set
#endif

#ifdef LOAD_FONT7
    fontsloaded |= 0x0080;  // Bit 7 set
#endif

#ifdef LOAD_FONT8
    fontsloaded |= 0x0100;  // Bit 8 set
#endif

#ifdef LOAD_FONT8N
    fontsloaded |= 0x0200;  // Bit 9 set
#endif

#ifdef SMOOTH_FONT
    fontsloaded |= 0x8000;  // Bit 15 set
#endif
}

/***************************************************************************************
** Function name:           begin
** Description:             Included for backwards compatibility
***************************************************************************************/
void TFT_eSPI::begin(uint8_t tc) {
    init(tc);
}

/***************************************************************************************
** Function name:           init (tc is tab colour for ST7735 displays only)
** Description:             Reset, then initialise the TFT display registers
***************************************************************************************/
void TFT_eSPI::init(uint8_t tc) {
    if (_booted) {
#if !defined(ESP32)
#ifdef TFT_CS
        cspinmask = (uint32_t)digitalPinToBitMask(TFT_CS);
#endif

#ifdef TFT_DC
        dcpinmask = (uint32_t)digitalPinToBitMask(TFT_DC);
#endif

#ifdef TFT_WR
        wrpinmask = (uint32_t)digitalPinToBitMask(TFT_WR);
#endif

#ifdef TFT_SCLK
        sclkpinmask = (uint32_t)digitalPinToBitMask(TFT_SCLK);
#endif

#ifdef TFT_SPI_OVERLAP
        // Overlap mode SD0=MISO, SD1=MOSI, CLK=SCLK must use D3 as CS
        //    pins(int8_t sck, int8_t miso, int8_t mosi, int8_t ss);
        // spi.pins(        6,          7,           8,          0);
        spi.pins(6, 7, 8, 0);
#endif

        spi.begin();  // This will set HMISO to input

#else
#if !defined(ESP32_PARALLEL)
#if defined(TFT_MOSI) && !defined(TFT_SPI_OVERLAP)
        spi.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1);
#else
        spi.begin();
#endif
#endif
#endif

        inTransaction = false;
        locked        = true;

        // SUPPORT_TRANSACTIONS is mandatory for ESP32 so the hal mutex is
        // toggled so the code here is for ESP8266 only
#if !defined(SUPPORT_TRANSACTIONS) && defined(ESP8266)
        spi.setBitOrder(MSBFIRST);
        spi.setDataMode(TFT_SPI_MODE);
        spi.setFrequency(SPI_FREQUENCY);
#endif

#if defined(ESP32_PARALLEL)
        digitalWrite(TFT_CS, LOW);  // Chip select low permanently
        pinMode(TFT_CS, OUTPUT);
#else
#ifdef TFT_CS
        // Set to output once again in case D6 (MISO) is used for CS
        digitalWrite(TFT_CS, HIGH);  // Chip select high (inactive)
        pinMode(TFT_CS, OUTPUT);
#else
        spi.setHwCs(1);  // Use hardware SS toggling
#endif
#endif

        // Set to output once again in case D6 (MISO) is used for DC
#ifdef TFT_DC
        digitalWrite(TFT_DC, HIGH);  // Data/Command high = data mode
        pinMode(TFT_DC, OUTPUT);
#endif

        _booted = false;
        spi_end();
    }  // end of: if just _booted

    // Toggle RST low to reset
    spi_begin();

#ifdef TFT_RST
    if (TFT_RST >= 0) {
        digitalWrite(TFT_RST, HIGH);
        delay(5);
        digitalWrite(TFT_RST, LOW);
        delay(20);
        digitalWrite(TFT_RST, HIGH);
    } else
        writecommand(TFT_SWRST);  // Software reset
#else
    writecommand(TFT_SWRST);  // Software reset
#endif

    spi_end();

    delay(150);  // Wait for reset to complete

    spi_begin();

    // This loads the driver specific initialisation code  <<<<<<<<<<<<<<<<<<<<<
    // ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<<
#if defined(ILI9341_DRIVER)
#include "ILI9341_Init.h"

#elif defined(ST7735_DRIVER)
    tabcolor = tc;
#include "ST7735_Init.h"

#elif defined(ILI9163_DRIVER)
#include "ILI9163_Init.h"

#elif defined(S6D02A1_DRIVER)
#include "S6D02A1_Init.h"

#elif defined(RPI_ILI9486_DRIVER)
#include "ILI9486_Init.h"

#elif defined(ILI9486_DRIVER)
#include "ILI9486_Init.h"

#elif defined(ILI9481_DRIVER)
#include "ILI9481_Init.h"

#elif defined(ILI9488_DRIVER)
#include "ILI9488_Init.h"

#elif defined(HX8357D_DRIVER)
#include "HX8357D_Init.h"

#elif defined(ST7789_DRIVER)
#include "ST7789_Init.h"

#elif defined(R61581_DRIVER)
#include "R61581_Init.h"

#endif

#ifdef TFT_INVERSION_ON
    writecommand(TFT_INVON);
#endif

#ifdef TFT_INVERSION_OFF
    writecommand(TFT_INVOFF);
#endif

    spi_end();

    setRotation(rotation);

#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
    digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
    pinMode(TFT_BL, OUTPUT);
#else
#if defined(TFT_BL) && defined(M5STACK)
    // Turn on the back-light LED
    digitalWrite(TFT_BL, HIGH);
    pinMode(TFT_BL, OUTPUT);
#endif
#endif
}

/***************************************************************************************
** Function name:           setRotation
** Description:             rotate the screen orientation m = 0-3 or 4-7 for BMP
*drawing
***************************************************************************************/
void TFT_eSPI::setRotation(uint8_t m) {
    spi_begin();

    // This loads the driver specific rotation code  <<<<<<<<<<<<<<<<<<<<< ADD
    // NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<<
#if defined(ILI9341_DRIVER)
#include "ILI9341_Rotation.h"

#elif defined(ST7735_DRIVER)
#include "ST7735_Rotation.h"

#elif defined(ILI9163_DRIVER)
#include "ILI9163_Rotation.h"

#elif defined(S6D02A1_DRIVER)
#include "S6D02A1_Rotation.h"

#elif defined(RPI_ILI9486_DRIVER)
#include "ILI9486_Rotation.h"

#elif defined(ILI9486_DRIVER)
#include "ILI9486_Rotation.h"

#elif defined(ILI9481_DRIVER)
#include "ILI9481_Rotation.h"

#elif defined(ILI9488_DRIVER)
#include "ILI9488_Rotation.h"

#elif defined(HX8357D_DRIVER)
#include "HX8357D_Rotation.h"

#elif defined(ST7789_DRIVER)
#include "ST7789_Rotation.h"

#elif defined(R61581_DRIVER)
#include "R61581_Rotation.h"

#endif

    delayMicroseconds(10);

    spi_end();

    addr_row = 0xFFFF;
    addr_col = 0xFFFF;
}

/***************************************************************************************
** Function name:           commandList, used for FLASH based lists only (e.g.
*ST7735)
** Description:             Get initialisation commands from FLASH and send to
*TFT
***************************************************************************************/
void TFT_eSPI::commandList(const uint8_t *addr) {
    uint8_t numCommands;
    uint8_t numArgs;
    uint8_t ms;

    numCommands = pgm_read_byte(addr++);  // Number of commands to follow

    while (numCommands--)  // For each command...
    {
        writecommand(pgm_read_byte(addr++));  // Read, issue command
        numArgs = pgm_read_byte(addr++);      // Number of args to follow
        ms      = numArgs & TFT_INIT_DELAY;  // If hibit set, delay follows args
        numArgs &= ~TFT_INIT_DELAY;          // Mask out delay bit

        while (numArgs--)  // For each argument...
        {
            writedata(pgm_read_byte(addr++));  // Read, issue argument
        }

        if (ms) {
            ms = pgm_read_byte(addr++);  // Read post-command delay time (ms)
            delay((ms == 255 ? 500 : ms));
        }
    }
}

/***************************************************************************************
** Function name:           spiwrite
** Description:             Write 8 bits to SPI port (legacy support only)
***************************************************************************************/
void TFT_eSPI::spiwrite(uint8_t c) {
    tft_Write_8(c);
}

/***************************************************************************************
** Function name:           writecommand
** Description:             Send an 8 bit command to the TFT
***************************************************************************************/
void TFT_eSPI::writecommand(uint8_t c) {
    spi_begin();  // CS_L;

    DC_C;

    tft_Write_8(c);

    DC_D;

    spi_end();  // CS_H;
}

/***************************************************************************************
** Function name:           writedata
** Description:             Send a 8 bit data value to the TFT
***************************************************************************************/
void TFT_eSPI::writedata(uint8_t d) {
    spi_begin();  // CS_L;

    DC_D;  // Play safe, but should already be in data mode

    tft_Write_8(d);

    CS_L;  // Allow more hold time for low VDI rail

    spi_end();  // CS_H;
}

/***************************************************************************************
** Function name:           readcommand8
** Description:             Read a 8 bit data value from an indexed command
*register
***************************************************************************************/
uint8_t TFT_eSPI::readcommand8(uint8_t cmd_function, uint8_t index) {
    uint8_t reg = 0;
#ifdef ESP32_PARALLEL

    writecommand(cmd_function);  // Sets DC and CS high

    busDir(dir_mask, INPUT);

    CS_L;

    // Read nth parameter (assumes caller discards 1st parameter or points index
    // to 2nd)
    while (index--) reg = readByte();

    busDir(dir_mask, OUTPUT);

    CS_H;

#else
    // for ILI9341 Interface II i.e. IM [3:0] = "1101"
    spi_begin_read();
    index = 0x10 + (index & 0x0F);

    DC_C;
    tft_Write_8(0xD9);
    DC_D;
    tft_Write_8(index);

    CS_H;  // Some displays seem to need CS to be pulsed here, or is just a
           // delay needed?
    CS_L;

    DC_C;
    tft_Write_8(cmd_function);
    DC_D;
    reg = tft_Read_8();

    spi_end_read();
#endif
    return reg;
}

/***************************************************************************************
** Function name:           readcommand16
** Description:             Read a 16 bit data value from an indexed command
*register
***************************************************************************************/
uint16_t TFT_eSPI::readcommand16(uint8_t cmd_function, uint8_t index) {
    uint32_t reg;

    reg = (readcommand8(cmd_function, index + 0) << 8);
    reg |= (readcommand8(cmd_function, index + 1) << 0);

    return reg;
}

/***************************************************************************************
** Function name:           readcommand32
** Description:             Read a 32 bit data value from an indexed command
*register
***************************************************************************************/
uint32_t TFT_eSPI::readcommand32(uint8_t cmd_function, uint8_t index) {
    uint32_t reg;

    reg = (readcommand8(cmd_function, index + 0) << 24);
    reg |= (readcommand8(cmd_function, index + 1) << 16);
    reg |= (readcommand8(cmd_function, index + 2) << 8);
    reg |= (readcommand8(cmd_function, index + 3) << 0);

    return reg;
}

/***************************************************************************************
** Function name:           read pixel (for SPI Interface II i.e. IM [3:0] =
*"1101")
** Description:             Read 565 pixel colours from a pixel
***************************************************************************************/
uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0) {
#if defined(ESP32_PARALLEL)

    readAddrWindow(x0, y0, 1, 1);  // Sets CS low

    // Set masked pins D0- D7 to input
    busDir(dir_mask, INPUT);

    // Dummy read to throw away don't care value
    readByte();

    // Fetch the 16 bit BRG pixel
    // uint16_t rgb = (readByte() << 8) | readByte();

#if defined(ILI9341_DRIVER) | defined(ILI9488_DRIVER)  // Read 3 bytes

    // Read window pixel 24 bit RGB values and fill in LS bits
    uint16_t rgb = ((readByte() & 0xF8) << 8) | ((readByte() & 0xFC) << 3) |
                   (readByte() >> 3);

    CS_H;

    // Set masked pins D0- D7 to output
    busDir(dir_mask, OUTPUT);

    return rgb;

#else  // ILI9481 16 bit read

    // Fetch the 16 bit BRG pixel
    uint16_t bgr = (readByte() << 8) | readByte();

    CS_H;

    // Set masked pins D0- D7 to output
    busDir(dir_mask, OUTPUT);

    // Swap Red and Blue (could check MADCTL setting to see if this is needed)
    return (bgr >> 11) | (bgr << 11) | (bgr & 0x7E0);
#endif

#else  // Not ESP32_PARALLEL

    spi_begin_read();

    readAddrWindow(x0, y0, 1, 1);  // Sets CS low

#ifdef TFT_SDA_READ
    begin_SDA_Read();
#endif

    // Dummy read to throw away don't care value
    tft_Read_8();

    //#if !defined (ILI9488_DRIVER)

    // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each
    // byte as the TFT stores colours as 18 bits
    uint8_t r = tft_Read_8();
    uint8_t g = tft_Read_8();
    uint8_t b = tft_Read_8();
    /*
      #else

        // The 6 colour bits are in MS 6 bits of each byte, but the ILI9488
      needs an extra clock pulse
        // so bits appear shifted right 1 bit, so mask the middle 6 bits then
      shift 1 place left uint8_t r = (tft_Read_8()&0x7E)<<1; uint8_t g =
      (tft_Read_8()&0x7E)<<1; uint8_t b = (tft_Read_8()&0x7E)<<1;

      #endif
    */
    CS_H;

#ifdef TFT_SDA_READ
    end_SDA_Read();
#endif

    spi_end_read();

    return color565(r, g, b);

#endif
}

/***************************************************************************************
** Function name:           read byte  - supports class functions
** Description:             Read a byte from ESP32 8 bit data port
***************************************************************************************/
// Bus MUST be set to input before calling this function!
uint8_t readByte(void) {
    uint8_t b = 0;

#ifdef ESP32_PARALLEL
    RD_L;
    uint32_t reg;            // Read all GPIO pins 0-31
    reg = gpio_input_get();  // Read three times to allow for bus access time
    reg = gpio_input_get();
    reg = gpio_input_get();  // Data should be stable now
    RD_H;

    // Check GPIO bits used and build value
    b = (((reg >> TFT_D0) & 1) << 0);
    b |= (((reg >> TFT_D1) & 1) << 1);
    b |= (((reg >> TFT_D2) & 1) << 2);
    b |= (((reg >> TFT_D3) & 1) << 3);
    b |= (((reg >> TFT_D4) & 1) << 4);
    b |= (((reg >> TFT_D5) & 1) << 5);
    b |= (((reg >> TFT_D6) & 1) << 6);
    b |= (((reg >> TFT_D7) & 1) << 7);
#endif

    return b;
}

/***************************************************************************************
** Function name:           masked GPIO direction control  - supports class
*functions
** Description:             Set masked ESP32 GPIO pins to input or output
***************************************************************************************/
void busDir(uint32_t mask, uint8_t mode) {
#ifdef ESP32_PARALLEL

    // Supports GPIO 0 - 31 on ESP32 only
    gpio_config_t gpio;

    gpio.pin_bit_mask = mask;
    gpio.mode         = GPIO_MODE_INPUT;
    gpio.pull_up_en   = GPIO_PULLUP_ENABLE;
    gpio.pull_down_en = GPIO_PULLDOWN_DISABLE;
    gpio.intr_type    = GPIO_INTR_DISABLE;

    if (mode == OUTPUT) gpio.mode = GPIO_MODE_OUTPUT;

    gpio_config(&gpio);

#endif
}

/***************************************************************************************
** Function name:           read rectangle (for SPI Interface II i.e. IM [3:0] =
*"1101")
** Description:             Read 565 pixel colours from a defined area
***************************************************************************************/
void TFT_eSPI::readRect(int32_t x, int32_t y, int32_t w, int32_t h,
                        uint16_t *data) {
    if ((x > _width) || (y > _height) || (w == 0) || (h == 0)) return;

#if defined(ESP32_PARALLEL)

    readAddrWindow(x, y, w, h);  // Sets CS low

    // Set masked pins D0- D7 to input
    busDir(dir_mask, INPUT);

    // Dummy read to throw away don't care value
    readByte();

    // Total pixel count
    uint32_t len = w * h;

#if defined(ILI9341_DRIVER) | defined(ILI9488_DRIVER)  // Read 3 bytes
    // Fetch the 24 bit RGB value
    while (len--) {
        // Assemble the RGB 16 bit colour
        uint16_t rgb = ((readByte() & 0xF8) << 8) | ((readByte() & 0xFC) << 3) |
                       (readByte() >> 3);

        // Swapped byte order for compatibility with pushRect()
        *data++ = (rgb << 8) | (rgb >> 8);
    }
#else  // ILI9481 reads as 16 bits
    // Fetch the 16 bit BRG pixels
    while (len--) {
        // Read the BRG 16 bit colour
        uint16_t bgr = (readByte() << 8) | readByte();

        // Swap Red and Blue (could check MADCTL setting to see if this is
        // needed)
        uint16_t rgb = (bgr >> 11) | (bgr << 11) | (bgr & 0x7E0);

        // Swapped byte order for compatibility with pushRect()
        *data++ = (rgb << 8) | (rgb >> 8);
    }
#endif
    CS_H;

    // Set masked pins D0- D7 to output
    busDir(dir_mask, OUTPUT);

#else  // Not ESP32_PARALLEL

    spi_begin_read();

    readAddrWindow(x, y, w, h);  // Sets CS low

#ifdef TFT_SDA_READ
    begin_SDA_Read();
#endif

    // Dummy read to throw away don't care value
    tft_Read_8();

    // Read window pixel 24 bit RGB values
    uint32_t len = w * h;
    while (len--) {

#if !defined(ILI9488_DRIVER)

        // Read the 3 RGB bytes, colour is actually only in the top 6 bits of
        // each byte as the TFT stores colours as 18 bits
        uint8_t r = tft_Read_8();
        uint8_t g = tft_Read_8();
        uint8_t b = tft_Read_8();

#else

        // The 6 colour bits are in LS 6 bits of each byte but we do not include
        // the extra clock pulse so we use a trick and mask the middle 6 bits of
        // the byte, then only shift 1 place left
        uint8_t r = (tft_Read_8() & 0x7E) << 1;
        uint8_t g = (tft_Read_8() & 0x7E) << 1;
        uint8_t b = (tft_Read_8() & 0x7E) << 1;

#endif

        // Swapped colour byte order for compatibility with pushRect()
        *data++ =
            (r & 0xF8) | (g & 0xE0) >> 5 | (b & 0xF8) << 5 | (g & 0x1C) << 11;
    }

    CS_H;

#ifdef TFT_SDA_READ
    end_SDA_Read();
#endif

    spi_end_read();

#endif
}

/***************************************************************************************
** Function name:           tft_Read_8
** Description:             Software SPI to read bidirectional SDA line
***************************************************************************************/
#if defined(ESP8266) && defined(TFT_SDA_READ)
uint8_t TFT_eSPI::tft_Read_8(void) {
    uint8_t ret  = 0;
    uint32_t reg = 0;

    for (uint8_t i = 0; i < 8; i++) {  // read results
        ret <<= 1;
        SCLK_L;
        if (digitalRead(TFT_MOSI)) ret |= 1;
        SCLK_H;
    }

    return ret;
}
#endif

/***************************************************************************************
** Function name:           beginSDA
** Description:             Detach SPI from pin to permit software SPI
***************************************************************************************/
#ifdef TFT_SDA_READ
void TFT_eSPI::begin_SDA_Read(void) {
#ifdef ESP32
    pinMatrixOutDetach(TFT_MOSI, false, false);
    pinMode(TFT_MOSI, INPUT);
    pinMatrixInAttach(TFT_MOSI, VSPIQ_IN_IDX, false);
#else  // ESP8266
#ifdef TFT_SPI_OVERLAP
    // Reads in overlap mode not supported
#else
    spi.end();
#endif
#endif
}
#endif

/***************************************************************************************
** Function name:           endSDA
** Description:             Attach SPI pins after software SPI
***************************************************************************************/
#ifdef TFT_SDA_READ
void TFT_eSPI::end_SDA_Read(void) {
#ifdef ESP32
    pinMode(TFT_MOSI, OUTPUT);
    pinMatrixOutAttach(TFT_MOSI, VSPID_OUT_IDX, false, false);
    pinMode(TFT_MISO, INPUT);
    pinMatrixInAttach(TFT_MISO, VSPIQ_IN_IDX, false);
#else
#ifdef TFT_SPI_OVERLAP
    spi.pins(6, 7, 8, 0);
#else
    spi.begin();
#endif
#endif
}
#endif

/***************************************************************************************
** Function name:           push rectangle (for SPI Interface II i.e. IM [3:0] =
*"1101")
** Description:             push 565 pixel colours into a defined area
***************************************************************************************/
void TFT_eSPI::pushRect(int32_t x, int32_t y, int32_t w, int32_t h,
                        uint16_t *data) {
    // Function deprecated, remains for backwards compatibility
    // pushImage() is better as it will crop partly off-screen image blocks
    pushImage(x, y, w, h, data);
}

/***************************************************************************************
** Function name:           pushImage
** Description:             plot 16 bit colour sprite or image onto TFT
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h,
                         uint16_t *data) {
    if ((x >= _width) || (y >= _height)) return;

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x  = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y  = 0;
    }

    if ((x + w) > _width) dw = _width - x;
    if ((y + h) > _height) dh = _height - y;

    if (dw < 1 || dh < 1) return;

    spi_begin();
    inTransaction = true;

    setWindow(x, y, x + dw - 1, y + dh - 1);

    data += dx + dy * w;

    while (dh--) {
        pushColors(data, dw, _swapBytes);
        data += w;
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           pushImage
** Description:             plot 16 bit sprite or image with 1 colour being
*transparent
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h,
                         uint16_t *data, uint16_t transp) {
    if ((x >= _width) || (y >= _height)) return;

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x  = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y  = 0;
    }

    if ((x + w) > _width) dw = _width - x;
    if ((y + h) > _height) dh = _height - y;

    if (dw < 1 || dh < 1) return;

    spi_begin();
    inTransaction = true;

    data += dx + dy * w;

    int32_t xe = x + dw - 1, ye = y + dh - 1;

    uint16_t lineBuf[dw];

    if (!_swapBytes) transp = transp >> 8 | transp << 8;

    while (dh--) {
        int32_t len   = dw;
        uint16_t *ptr = data;
        int32_t px    = x;
        boolean move  = true;
        uint16_t np   = 0;

        while (len--) {
            if (transp != *ptr) {
                if (move) {
                    move = false;
                    setWindow(px, y, xe, ye);
                }
                lineBuf[np] = *ptr;
                np++;
            } else {
                move = true;
                if (np) {
                    pushColors((uint16_t *)lineBuf, np, _swapBytes);
                    np = 0;
                }
            }
            px++;
            ptr++;
        }
        if (np) pushColors((uint16_t *)lineBuf, np, _swapBytes);

        y++;
        data += w;
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           pushImage - for FLASH (PROGMEM) stored images
** Description:             plot 16 bit image
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h,
                         const uint16_t *data) {
#ifdef ESP32
    pushImage(x, y, w, h, (uint16_t *)data);
#else
    // Partitioned memory FLASH processor
    if ((x >= _width) || (y >= _height)) return;

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x  = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y  = 0;
    }

    if ((x + w) > _width) dw = _width - x;
    if ((y + h) > _height) dh = _height - y;

    if (dw < 1 || dh < 1) return;

    spi_begin();
    inTransaction = true;

    data += dx + dy * w;

    uint16_t buffer[64];
    uint16_t *pix_buffer = buffer;

    setWindow(x, y, x + dw - 1, y + dh - 1);

    // Work out the number whole buffers to send
    uint16_t nb = (dw * dh) / 64;

    // Fill and send "nb" buffers to TFT
    for (int32_t i = 0; i < nb; i++) {
        for (int32_t j = 0; j < 64; j++) {
            pix_buffer[j] = pgm_read_word(&data[i * 64 + j]);
        }
        pushColors(pix_buffer, 64, _swapBytes);
    }

    // Work out number of pixels not yet sent
    uint16_t np = (dw * dh) % 64;

    // Send any partial buffer left over
    if (np) {
        for (int32_t i = 0; i < np; i++) {
            pix_buffer[i] = pgm_read_word(&data[nb * 64 + i]);
        }
        pushColors(pix_buffer, np, _swapBytes);
    }

    inTransaction = false;
    spi_end();
#endif  // if ESP32 else ESP8266 check
}

/***************************************************************************************
** Function name:           pushImage - for FLASH (PROGMEM) stored images
** Description:             plot 16 bit image with 1 colour being transparent
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h,
                         const uint16_t *data, uint16_t transp) {
#ifdef ESP32
    pushImage(x, y, w, h, (uint16_t *)data, transp);
#else
    // Partitioned memory FLASH processor
    if ((x >= _width) || (y >= (int32_t)_height)) return;

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x  = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y  = 0;
    }

    if ((x + w) > _width) dw = _width - x;
    if ((y + h) > _height) dh = _height - y;

    if (dw < 1 || dh < 1) return;

    spi_begin();
    inTransaction = true;

    data += dx + dy * w;

    int32_t xe = x + dw - 1, ye = y + dh - 1;

    uint16_t lineBuf[dw];

    if (!_swapBytes) transp = transp >> 8 | transp << 8;

    while (dh--) {
        int32_t len   = dw;
        uint16_t *ptr = (uint16_t *)data;
        int32_t px    = x;
        boolean move  = true;

        uint16_t np = 0;

        while (len--) {
            uint16_t color = pgm_read_word(ptr);
            if (transp != color) {
                if (move) {
                    move = false;
                    setWindow(px, y, xe, ye);
                }
                lineBuf[np] = color;
                np++;
            } else {
                move = true;
                if (np) {
                    pushColors(lineBuf, np, _swapBytes);
                    np = 0;
                }
            }
            px++;
            ptr++;
        }
        if (np) pushColors(lineBuf, np, _swapBytes);

        y++;
        data += w;
    }

    inTransaction = false;
    spi_end();
#endif  // if ESP32 else ESP8266 check
}

/***************************************************************************************
** Function name:           pushImage
** Description:             plot 8 bit image or sprite using a line buffer
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h,
                         uint8_t *data, bool bpp8) {
    if ((x >= _width) || (y >= (int32_t)_height)) return;

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x  = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y  = 0;
    }

    if ((x + w) > _width) dw = _width - x;
    if ((y + h) > _height) dh = _height - y;

    if (dw < 1 || dh < 1) return;

    spi_begin();
    inTransaction = true;

    setWindow(x, y, x + dw - 1, y + dh - 1);  // Sets CS low and sent RAMWR

    // Line buffer makes plotting faster
    uint16_t lineBuf[dw];

    if (bpp8) {
        uint8_t blue[] = {0, 11, 21,
                          31};  // blue 2 to 5 bit colour lookup table

        _lastColor = -1;  // Set to illegal value

        // Used to store last shifted colour
        uint8_t msbColor = 0;
        uint8_t lsbColor = 0;

        data += dx + dy * w;
        while (dh--) {
            uint32_t len     = dw;
            uint8_t *ptr     = data;
            uint8_t *linePtr = (uint8_t *)lineBuf;

            while (len--) {
                uint32_t color = *ptr++;

                // Shifts are slow so check if colour has changed first
                if (color != _lastColor) {
                    //          =====Green===== ===============Red==============
                    msbColor = (color & 0x1C) >> 2 | (color & 0xC0) >> 3 |
                               (color & 0xE0);
                    //          =====Green=====    =======Blue======
                    lsbColor   = (color & 0x1C) << 3 | blue[color & 0x03];
                    _lastColor = color;
                }

                *linePtr++ = msbColor;
                *linePtr++ = lsbColor;
            }

            pushColors(lineBuf, dw, false);

            data += w;
        }
    } else {
        while (dh--) {
            w = (w + 7) & 0xFFF8;

            int32_t len      = dw;
            uint8_t *ptr     = data;
            uint8_t *linePtr = (uint8_t *)lineBuf;
            uint8_t bits     = 8;
            while (len > 0) {
                if (len < 8) bits = len;
                uint32_t xp = dx;
                for (uint16_t i = 0; i < bits; i++) {
                    uint8_t col =
                        (ptr[(xp + dy * w) >> 3] << (xp & 0x7)) & 0x80;
                    if (col) {
                        *linePtr++ = bitmap_fg >> 8;
                        *linePtr++ = (uint8_t)bitmap_fg;
                    } else {
                        *linePtr++ = bitmap_bg >> 8;
                        *linePtr++ = (uint8_t)bitmap_bg;
                    }
                    // if (col) drawPixel((dw-len)+xp,h-dh,bitmap_fg);
                    // else     drawPixel((dw-len)+xp,h-dh,bitmap_bg);
                    xp++;
                }
                ptr++;
                len -= 8;
            }

            pushColors(lineBuf, dw, false);

            dy++;
        }
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           pushImage
** Description:             plot 8 or 1 bit image or sprite with a transparent
*colour
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h,
                         uint8_t *data, uint8_t transp, bool bpp8) {
    if ((x >= _width) || (y >= _height)) return;

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x  = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y  = 0;
    }

    if ((x + w) > _width) dw = _width - x;
    if ((y + h) > _height) dh = _height - y;

    if (dw < 1 || dh < 1) return;

    spi_begin();
    inTransaction = true;

    int32_t xe = x + dw - 1, ye = y + dh - 1;

    // Line buffer makes plotting faster
    uint16_t lineBuf[dw];

    if (bpp8) {
        data += dx + dy * w;

        uint8_t blue[] = {0, 11, 21,
                          31};  // blue 2 to 5 bit colour lookup table

        _lastColor = -1;  // Set to illegal value

        // Used to store last shifted colour
        uint8_t msbColor = 0;
        uint8_t lsbColor = 0;

        // int32_t spx = x, spy = y;

        while (dh--) {
            int32_t len      = dw;
            uint8_t *ptr     = data;
            uint8_t *linePtr = (uint8_t *)lineBuf;

            int32_t px   = x;
            boolean move = true;
            uint16_t np  = 0;

            while (len--) {
                if (transp != *ptr) {
                    if (move) {
                        move = false;
                        setWindow(px, y, xe, ye);
                    }
                    uint8_t color = *ptr;

                    // Shifts are slow so check if colour has changed first
                    if (color != _lastColor) {
                        //          =====Green=====
                        //          ===============Red==============
                        msbColor = (color & 0x1C) >> 2 | (color & 0xC0) >> 3 |
                                   (color & 0xE0);
                        //          =====Green=====    =======Blue======
                        lsbColor   = (color & 0x1C) << 3 | blue[color & 0x03];
                        _lastColor = color;
                    }
                    *linePtr++ = msbColor;
                    *linePtr++ = lsbColor;
                    np++;
                } else {
                    move = true;
                    if (np) {
                        pushColors(lineBuf, np, false);
                        linePtr = (uint8_t *)lineBuf;
                        np      = 0;
                    }
                }
                px++;
                ptr++;
            }

            if (np) pushColors(lineBuf, np, false);

            y++;
            data += w;
        }
    } else {
        w = (w + 7) & 0xFFF8;
        while (dh--) {
            int32_t px   = x;
            boolean move = true;
            uint16_t np  = 0;
            int32_t len  = dw;
            uint8_t *ptr = data;
            uint8_t bits = 8;
            while (len > 0) {
                if (len < 8) bits = len;
                uint32_t xp = dx;
                uint32_t yp = (dy * w) >> 3;
                for (uint16_t i = 0; i < bits; i++) {
                    // uint8_t col = (ptr[(xp + dy * w)>>3] << (xp & 0x7)) &
                    // 0x80;
                    if ((ptr[(xp >> 3) + yp] << (xp & 0x7)) & 0x80) {
                        if (move) {
                            move = false;
                            setWindow(px, y, xe, ye);
                        }
                        np++;
                    } else {
                        if (np) {
                            pushColor(bitmap_fg, np);
                            np   = 0;
                            move = true;
                        }
                    }
                    px++;
                    xp++;
                }
                ptr++;
                len -= 8;
            }
            if (np) pushColor(bitmap_fg, np);
            y++;
            dy++;
        }
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           setSwapBytes
** Description:             Used by 16 bit pushImage() to swap byte order in
*colours
***************************************************************************************/
void TFT_eSPI::setSwapBytes(bool swap) {
    _swapBytes = swap;
}

/***************************************************************************************
** Function name:           getSwapBytes
** Description:             Return the swap byte order for colours
***************************************************************************************/
bool TFT_eSPI::getSwapBytes(void) {
    return _swapBytes;
}

/***************************************************************************************
** Function name:           read rectangle (for SPI Interface II i.e. IM [3:0] =
*"1101")
** Description:             Read RGB pixel colours from a defined area
***************************************************************************************/
// If w and h are 1, then 1 pixel is read, *data array size must be 3 bytes per
// pixel
void TFT_eSPI::readRectRGB(int32_t x0, int32_t y0, int32_t w, int32_t h,
                           uint8_t *data) {
#if defined(ESP32_PARALLEL)

    // ESP32 parallel bus supported yet

#else  // Not ESP32_PARALLEL

    spi_begin_read();

    readAddrWindow(x0, y0, w, h);  // Sets CS low

#ifdef TFT_SDA_READ
    begin_SDA_Read();
#endif

    // Dummy read to throw away don't care value
    tft_Read_8();

    // Read window pixel 24 bit RGB values, buffer must be set in sketch to 3 *
    // w * h
    uint32_t len = w * h;
    while (len--) {

#if !defined(ILI9488_DRIVER)

        // Read the 3 RGB bytes, colour is actually only in the top 6 bits of
        // each byte as the TFT stores colours as 18 bits
        *data++ = tft_Read_8();
        *data++ = tft_Read_8();
        *data++ = tft_Read_8();

#else

        // The 6 colour bits are in MS 6 bits of each byte, but the ILI9488
        // needs an extra clock pulse so bits appear shifted right 1 bit, so
        // mask the middle 6 bits then shift 1 place left
        *data++ = (tft_Read_8() & 0x7E) << 1;
        *data++ = (tft_Read_8() & 0x7E) << 1;
        *data++ = (tft_Read_8() & 0x7E) << 1;

#endif
    }

    CS_H;

#ifdef TFT_SDA_READ
    end_SDA_Read();
#endif

    spi_end_read();

#endif
}

/***************************************************************************************
** Function name:           drawCircle
** Description:             Draw a circle outline
***************************************************************************************/
// Optimised midpoint circle algorithm
void TFT_eSPI::drawCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color) {
    int32_t x  = 0;
    int32_t dx = 1;
    int32_t dy = r + r;
    int32_t p  = -(r >> 1);

    spi_begin();
    inTransaction = true;

    // These are ordered to minimise coordinate changes in x or y
    // drawPixel can then send fewer bounding box commands
    drawPixel(x0 + r, y0, color);
    drawPixel(x0 - r, y0, color);
    drawPixel(x0, y0 - r, color);
    drawPixel(x0, y0 + r, color);

    while (x < r) {
        if (p >= 0) {
            dy -= 2;
            p -= dy;
            r--;
        }

        dx += 2;
        p += dx;

        x++;

        // These are ordered to minimise coordinate changes in x or y
        // drawPixel can then send fewer bounding box commands
        drawPixel(x0 + x, y0 + r, color);
        drawPixel(x0 - x, y0 + r, color);
        drawPixel(x0 - x, y0 - r, color);
        drawPixel(x0 + x, y0 - r, color);

        drawPixel(x0 + r, y0 + x, color);
        drawPixel(x0 - r, y0 + x, color);
        drawPixel(x0 - r, y0 - x, color);
        drawPixel(x0 + r, y0 - x, color);
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           drawCircleHelper
** Description:             Support function for circle drawing
***************************************************************************************/
void TFT_eSPI::drawCircleHelper(int32_t x0, int32_t y0, int32_t r,
                                uint8_t cornername, uint32_t color) {
    int32_t f     = 1 - r;
    int32_t ddF_x = 1;
    int32_t ddF_y = -2 * r;
    int32_t x     = 0;

    while (x < r) {
        if (f >= 0) {
            r--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;
        if (cornername & 0x4) {
            drawPixel(x0 + x, y0 + r, color);
            drawPixel(x0 + r, y0 + x, color);
        }
        if (cornername & 0x2) {
            drawPixel(x0 + x, y0 - r, color);
            drawPixel(x0 + r, y0 - x, color);
        }
        if (cornername & 0x8) {
            drawPixel(x0 - r, y0 + x, color);
            drawPixel(x0 - x, y0 + r, color);
        }
        if (cornername & 0x1) {
            drawPixel(x0 - r, y0 - x, color);
            drawPixel(x0 - x, y0 - r, color);
        }
    }
}

/***************************************************************************************
** Function name:           fillCircle
** Description:             draw a filled circle
***************************************************************************************/
// Optimised midpoint circle algorithm, changed to horizontal lines (faster in
// sprites)
void TFT_eSPI::fillCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color) {
    int32_t x  = 0;
    int32_t dx = 1;
    int32_t dy = r + r;
    int32_t p  = -(r >> 1);

    spi_begin();
    inTransaction = true;

    drawFastHLine(x0 - r, y0, dy + 1, color);

    while (x < r) {
        if (p >= 0) {
            dy -= 2;
            p -= dy;
            r--;
        }

        dx += 2;
        p += dx;

        x++;

        drawFastHLine(x0 - r, y0 + x, 2 * r + 1, color);
        drawFastHLine(x0 - r, y0 - x, 2 * r + 1, color);
        drawFastHLine(x0 - x, y0 + r, 2 * x + 1, color);
        drawFastHLine(x0 - x, y0 - r, 2 * x + 1, color);
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           fillCircleHelper
** Description:             Support function for filled circle drawing
***************************************************************************************/
// Used to support drawing roundrects, changed to horizontal lines (faster in
// sprites)
void TFT_eSPI::fillCircleHelper(int32_t x0, int32_t y0, int32_t r,
                                uint8_t cornername, int32_t delta,
                                uint32_t color) {
    int32_t f     = 1 - r;
    int32_t ddF_x = 1;
    int32_t ddF_y = -r - r;
    int32_t y     = 0;

    delta++;
    while (y < r) {
        if (f >= 0) {
            r--;
            ddF_y += 2;
            f += ddF_y;
        }
        y++;
        // x++;
        ddF_x += 2;
        f += ddF_x;

        if (cornername & 0x1) {
            drawFastHLine(x0 - r, y0 + y, r + r + delta, color);
            drawFastHLine(x0 - y, y0 + r, y + y + delta, color);
        }
        if (cornername & 0x2) {
            drawFastHLine(x0 - r, y0 - y, r + r + delta, color);  // 11995, 1090
            drawFastHLine(x0 - y, y0 - r, y + y + delta, color);
        }
    }
}

/***************************************************************************************
** Function name:           drawEllipse
** Description:             Draw a ellipse outline
***************************************************************************************/
void TFT_eSPI::drawEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry,
                           uint16_t color) {
    if (rx < 2) return;
    if (ry < 2) return;
    int32_t x, y;
    int32_t rx2 = rx * rx;
    int32_t ry2 = ry * ry;
    int32_t fx2 = 4 * rx2;
    int32_t fy2 = 4 * ry2;
    int32_t s;

    spi_begin();
    inTransaction = true;

    for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y;
         x++) {
        // These are ordered to minimise coordinate changes in x or y
        // drawPixel can then send fewer bounding box commands
        drawPixel(x0 + x, y0 + y, color);
        drawPixel(x0 - x, y0 + y, color);
        drawPixel(x0 - x, y0 - y, color);
        drawPixel(x0 + x, y0 - y, color);
        if (s >= 0) {
            s += fx2 * (1 - y);
            y--;
        }
        s += ry2 * ((4 * x) + 6);
    }

    for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x;
         y++) {
        // These are ordered to minimise coordinate changes in x or y
        // drawPixel can then send fewer bounding box commands
        drawPixel(x0 + x, y0 + y, color);
        drawPixel(x0 - x, y0 + y, color);
        drawPixel(x0 - x, y0 - y, color);
        drawPixel(x0 + x, y0 - y, color);
        if (s >= 0) {
            s += fy2 * (1 - x);
            x--;
        }
        s += rx2 * ((4 * y) + 6);
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           fillEllipse
** Description:             draw a filled ellipse
***************************************************************************************/
void TFT_eSPI::fillEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry,
                           uint16_t color) {
    if (rx < 2) return;
    if (ry < 2) return;
    int32_t x, y;
    int32_t rx2 = rx * rx;
    int32_t ry2 = ry * ry;
    int32_t fx2 = 4 * rx2;
    int32_t fy2 = 4 * ry2;
    int32_t s;

    spi_begin();
    inTransaction = true;

    for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y;
         x++) {
        drawFastHLine(x0 - x, y0 - y, x + x + 1, color);
        drawFastHLine(x0 - x, y0 + y, x + x + 1, color);

        if (s >= 0) {
            s += fx2 * (1 - y);
            y--;
        }
        s += ry2 * ((4 * x) + 6);
    }

    for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x;
         y++) {
        drawFastHLine(x0 - x, y0 - y, x + x + 1, color);
        drawFastHLine(x0 - x, y0 + y, x + x + 1, color);

        if (s >= 0) {
            s += fy2 * (1 - x);
            x--;
        }
        s += rx2 * ((4 * y) + 6);
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           fillScreen
** Description:             Clear the screen to defined colour
***************************************************************************************/
void TFT_eSPI::fillScreen(uint32_t color) {
    fillRect(0, 0, _width, _height, color);
}

/***************************************************************************************
** Function name:           drawRect
** Description:             Draw a rectangle outline
***************************************************************************************/
// Draw a rectangle
void TFT_eSPI::drawRect(int32_t x, int32_t y, int32_t w, int32_t h,
                        uint32_t color) {
    spi_begin();
    inTransaction = true;

    drawFastHLine(x, y, w, color);
    drawFastHLine(x, y + h - 1, w, color);
    // Avoid drawing corner pixels twice
    drawFastVLine(x, y + 1, h - 2, color);
    drawFastVLine(x + w - 1, y + 1, h - 2, color);

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           drawRoundRect
** Description:             Draw a rounded corner rectangle outline
***************************************************************************************/
// Draw a rounded rectangle
void TFT_eSPI::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
                             int32_t r, uint32_t color) {
    spi_begin();
    inTransaction = true;

    // smarter version
    drawFastHLine(x + r, y, w - r - r, color);          // Top
    drawFastHLine(x + r, y + h - 1, w - r - r, color);  // Bottom
    drawFastVLine(x, y + r, h - r - r, color);          // Left
    drawFastVLine(x + w - 1, y + r, h - r - r, color);  // Right
    // draw four corners
    drawCircleHelper(x + r, y + r, r, 1, color);
    drawCircleHelper(x + w - r - 1, y + r, r, 2, color);
    drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color);
    drawCircleHelper(x + r, y + h - r - 1, r, 8, color);

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           fillRoundRect
** Description:             Draw a rounded corner filled rectangle
***************************************************************************************/
// Fill a rounded rectangle, changed to horizontal lines (faster in sprites)
void TFT_eSPI::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
                             int32_t r, uint32_t color) {
    spi_begin();
    inTransaction = true;

    // smarter version
    fillRect(x, y + r, w, h - r - r, color);

    // draw four corners
    fillCircleHelper(x + r, y + h - r - 1, r, 1, w - r - r - 1, color);
    fillCircleHelper(x + r, y + r, r, 2, w - r - r - 1, color);

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           drawTriangle
** Description:             Draw a triangle outline using 3 arbitrary points
***************************************************************************************/
// Draw a triangle
void TFT_eSPI::drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1,
                            int32_t x2, int32_t y2, uint32_t color) {
    spi_begin();
    inTransaction = true;

    drawLine(x0, y0, x1, y1, color);
    drawLine(x1, y1, x2, y2, color);
    drawLine(x2, y2, x0, y0, color);

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           fillTriangle
** Description:             Draw a filled triangle using 3 arbitrary points
***************************************************************************************/
// Fill a triangle - original Adafruit function works well and code footprint is
// small
void TFT_eSPI::fillTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1,
                            int32_t x2, int32_t y2, uint32_t color) {
    int32_t a, b, y, last;

    // Sort coordinates by Y order (y2 >= y1 >= y0)
    if (y0 > y1) {
        swap_coord(y0, y1);
        swap_coord(x0, x1);
    }
    if (y1 > y2) {
        swap_coord(y2, y1);
        swap_coord(x2, x1);
    }
    if (y0 > y1) {
        swap_coord(y0, y1);
        swap_coord(x0, x1);
    }

    if (y0 == y2) {  // Handle awkward all-on-same-line case as its own thing
        a = b = x0;
        if (x1 < a)
            a = x1;
        else if (x1 > b)
            b = x1;
        if (x2 < a)
            a = x2;
        else if (x2 > b)
            b = x2;
        drawFastHLine(a, y0, b - a + 1, color);
        return;
    }

    spi_begin();
    inTransaction = true;

    int32_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0,
            dx12 = x2 - x1, dy12 = y2 - y1, sa = 0, sb = 0;

    // For upper part of triangle, find scanline crossings for segments
    // 0-1 and 0-2.  If y1=y2 (flat-bottomed triangle), the scanline y1
    // is included here (and second loop will be skipped, avoiding a /0
    // error there), otherwise scanline y1 is skipped here and handled
    // in the second loop...which also avoids a /0 error here if y0=y1
    // (flat-topped triangle).
    if (y1 == y2)
        last = y1;  // Include y1 scanline
    else
        last = y1 - 1;  // Skip it

    for (y = y0; y <= last; y++) {
        a = x0 + sa / dy01;
        b = x0 + sb / dy02;
        sa += dx01;
        sb += dx02;

        if (a > b) swap_coord(a, b);
        drawFastHLine(a, y, b - a + 1, color);
    }

    // For lower part of triangle, find scanline crossings for segments
    // 0-2 and 1-2.  This loop is skipped if y1=y2.
    sa = dx12 * (y - y1);
    sb = dx02 * (y - y0);
    for (; y <= y2; y++) {
        a = x1 + sa / dy12;
        b = x0 + sb / dy02;
        sa += dx12;
        sb += dx02;

        if (a > b) swap_coord(a, b);
        drawFastHLine(a, y, b - a + 1, color);
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           drawBitmap
** Description:             Draw an image stored in an array on the TFT
***************************************************************************************/
void TFT_eSPI::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap,
                          int16_t w, int16_t h, uint16_t color,
                          uint16_t bgcolor) {
    spi_begin();
    inTransaction = true;

    int32_t i, j, byteWidth = (w + 7) / 8;

    for (j = 0; j < h; j++) {
        for (i = 0; i < w; i++) {
            if (pgm_read_byte(bitmap + j * byteWidth + i / 8) &
                (128 >> (i & 7)))
                drawPixel(x + i, y + j, color);
            else
                drawPixel(x + i, y + j, bgcolor);
        }
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           drawXBitmap
** Description:             Draw an image stored in an XBM array onto the TFT
***************************************************************************************/
void TFT_eSPI::drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap,
                           int16_t w, int16_t h, uint16_t color) {
    spi_begin();
    inTransaction = true;

    int32_t i, j, byteWidth = (w + 7) / 8;

    for (j = 0; j < h; j++) {
        for (i = 0; i < w; i++) {
            if (pgm_read_byte(bitmap + j * byteWidth + i / 8) &
                (1 << (i & 7))) {
                drawPixel(x + i, y + j, color);
            }
        }
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           drawXBitmap
** Description:             Draw an XBM image with foreground and background
*colors
***************************************************************************************/
void TFT_eSPI::drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap,
                           int16_t w, int16_t h, uint16_t color,
                           uint16_t bgcolor) {
    spi_begin();
    inTransaction = true;

    int32_t i, j, byteWidth = (w + 7) / 8;

    for (j = 0; j < h; j++) {
        for (i = 0; i < w; i++) {
            if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (1 << (i & 7)))
                drawPixel(x + i, y + j, color);
            else
                drawPixel(x + i, y + j, bgcolor);
        }
    }

    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           setCursor
** Description:             Set the text cursor x,y position
***************************************************************************************/
void TFT_eSPI::setCursor(int16_t x, int16_t y) {
    cursor_x = x;
    cursor_y = y;
}

/***************************************************************************************
** Function name:           setCursor
** Description:             Set the text cursor x,y position and font
***************************************************************************************/
void TFT_eSPI::setCursor(int16_t x, int16_t y, uint8_t font) {
    textfont = font;
    cursor_x = x;
    cursor_y = y;
}

/***************************************************************************************
** Function name:           getCursorX
** Description:             Get the text cursor x position
***************************************************************************************/
int16_t TFT_eSPI::getCursorX(void) {
    return cursor_x;
}

/***************************************************************************************
** Function name:           getCursorY
** Description:             Get the text cursor y position
***************************************************************************************/
int16_t TFT_eSPI::getCursorY(void) {
    return cursor_y;
}

/***************************************************************************************
** Function name:           setTextSize
** Description:             Set the text size multiplier
***************************************************************************************/
void TFT_eSPI::setTextSize(uint8_t s) {
    if (s > 7)
        s = 7;  // Limit the maximum size multiplier so byte variables can be
                // used for rendering
    textsize = (s > 0) ? s : 1;  // Don't allow font size 0
}

/***************************************************************************************
** Function name:           setTextColor
** Description:             Set the font foreground colour (background is
*transparent)
***************************************************************************************/
void TFT_eSPI::setTextColor(uint16_t c) {
    // For 'transparent' background, we'll set the bg
    // to the same as fg instead of using a flag
    textcolor = textbgcolor = c;
}

/***************************************************************************************
** Function name:           setTextColor
** Description:             Set the font foreground and background colour
***************************************************************************************/
void TFT_eSPI::setTextColor(uint16_t c, uint16_t b) {
    textcolor   = c;
    textbgcolor = b;
}

/***************************************************************************************
** Function name:           setPivot
** Description:             Set the pivot point on the TFT
*************************************************************************************x*/
void TFT_eSPI::setPivot(int16_t x, int16_t y) {
    _xpivot = x;
    _ypivot = y;
}

/***************************************************************************************
** Function name:           getPivotX
** Description:             Get the x pivot position
***************************************************************************************/
int16_t TFT_eSPI::getPivotX(void) {
    return _xpivot;
}

/***************************************************************************************
** Function name:           getPivotY
** Description:             Get the y pivot position
***************************************************************************************/
int16_t TFT_eSPI::getPivotY(void) {
    return _ypivot;
}

/***************************************************************************************
** Function name:           setBitmapColor
** Description:             Set the foreground foreground and background colour
***************************************************************************************/
void TFT_eSPI::setBitmapColor(uint16_t c, uint16_t b) {
    if (c == b) b = ~c;
    bitmap_fg = c;
    bitmap_bg = b;
}

/***************************************************************************************
** Function name:           setTextWrap
** Description:             Define if text should wrap at end of line
***************************************************************************************/
void TFT_eSPI::setTextWrap(boolean wrapX, boolean wrapY) {
    textwrapX = wrapX;
    textwrapY = wrapY;
}

/***************************************************************************************
** Function name:           setTextDatum
** Description:             Set the text position reference datum
***************************************************************************************/
void TFT_eSPI::setTextDatum(uint8_t d) {
    textdatum = d;
}

/***************************************************************************************
** Function name:           setTextPadding
** Description:             Define padding width (aids erasing old text and
*numbers)
***************************************************************************************/
void TFT_eSPI::setTextPadding(uint16_t x_width) {
    padX = x_width;
}

/***************************************************************************************
** Function name:           getRotation
** Description:             Return the rotation value (as used by setRotation())
***************************************************************************************/
uint8_t TFT_eSPI::getRotation(void) {
    return rotation;
}

/***************************************************************************************
** Function name:           getTextDatum
** Description:             Return the text datum value (as used by
*setTextDatum())
***************************************************************************************/
uint8_t TFT_eSPI::getTextDatum(void) {
    return textdatum;
}

/***************************************************************************************
** Function name:           width
** Description:             Return the pixel width of display (per current
*rotation)
***************************************************************************************/
// Return the size of the display (per current rotation)
int16_t TFT_eSPI::width(void) {
    return _width;
}

/***************************************************************************************
** Function name:           height
** Description:             Return the pixel height of display (per current
*rotation)
***************************************************************************************/
int16_t TFT_eSPI::height(void) {
    return _height;
}

/***************************************************************************************
** Function name:           textWidth
** Description:             Return the width in pixels of a string in a given
*font
***************************************************************************************/
int16_t TFT_eSPI::textWidth(const String &string) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return textWidth(buffer, textfont);
}

int16_t TFT_eSPI::textWidth(const String &string, uint8_t font) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return textWidth(buffer, font);
}

int16_t TFT_eSPI::textWidth(const char *string) {
    return textWidth(string, textfont);
}

int16_t TFT_eSPI::textWidth(const char *string, uint8_t font) {
    int32_t str_width = 0;
    uint16_t uniCode  = 0;

#ifdef SMOOTH_FONT
    if (fontLoaded) {
        while (*string) {
            uniCode = decodeUTF8(*string++);
            if (uniCode) {
                if (uniCode == 0x20)
                    str_width += gFont.spaceWidth;
                else {
                    uint16_t gNum = 0;
                    bool found    = getUnicodeIndex(uniCode, &gNum);
                    if (found) {
                        if (str_width == 0 && gdX[gNum] < 0)
                            str_width -= gdX[gNum];
                        if (*string || isDigits)
                            str_width += gxAdvance[gNum];
                        else
                            str_width += (gdX[gNum] + gWidth[gNum]);
                    } else
                        str_width += gFont.spaceWidth + 1;
                }
            }
        }
        isDigits = false;
        return str_width;
    }
#endif

    if (font > 1 && font < 9) {
        char *widthtable = (char *)pgm_read_dword(&(fontdata[font].widthtbl)) -
                           32;  // subtract the 32 outside the loop

        while (*string) {
            uniCode = *(string++);
            if (uniCode > 31 && uniCode < 128)
                str_width += pgm_read_byte(
                    widthtable +
                    uniCode);  // Normally we need to subtract 32 from uniCode
            else
                str_width += pgm_read_byte(
                    widthtable + 32);  // Set illegal character = space width
        }
    } else {
#ifdef LOAD_GFXFF
        if (gfxFont)  // New font
        {
            while (*string) {
                uniCode = decodeUTF8(*string++);
                if ((uniCode >= pgm_read_word(&gfxFont->first)) &&
                    (uniCode <= pgm_read_word(&gfxFont->last))) {
                    uniCode -= pgm_read_word(&gfxFont->first);
                    GFXglyph *glyph = &(
                        ((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[uniCode]);
                    // If this is not the  last character or is a digit then use
                    // xAdvance
                    if (*string || isDigits)
                        str_width += pgm_read_byte(&glyph->xAdvance);
                    // Else use the offset plus width since this can be bigger
                    // than xAdvance
                    else
                        str_width += ((int8_t)pgm_read_byte(&glyph->xOffset) +
                                      pgm_read_byte(&glyph->width));
                }
            }
        } else
#endif
        {
#ifdef LOAD_GLCD
            while (*string++) str_width += 6;
#endif
        }
    }
    isDigits = false;
    return str_width * textsize;
}

/***************************************************************************************
** Function name:           fontsLoaded
** Description:             return an encoded 16 bit value showing the fonts
*loaded
***************************************************************************************/
// Returns a value showing which fonts are loaded (bit N set =  Font N loaded)

uint16_t TFT_eSPI::fontsLoaded(void) {
    return fontsloaded;
}

/***************************************************************************************
** Function name:           fontHeight
** Description:             return the height of a font (yAdvance for free
*fonts)
***************************************************************************************/
int16_t TFT_eSPI::fontHeight(int16_t font) {
#ifdef SMOOTH_FONT
    if (fontLoaded) return gFont.yAdvance;
#endif

#ifdef LOAD_GFXFF
    if (font == 1) {
        if (gfxFont)  // New font
        {
            return pgm_read_byte(&gfxFont->yAdvance) * textsize;
        }
    }
#endif
    return pgm_read_byte(&fontdata[font].height) * textsize;
}

int16_t TFT_eSPI::fontHeight(void) {
    return fontHeight(textfont);
}

/***************************************************************************************
** Function name:           drawChar
** Description:             draw a single character in the Adafruit GLCD font
***************************************************************************************/
void TFT_eSPI::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color,
                        uint32_t bg, uint8_t size) {
    if ((x >= _width) ||             // Clip right
        (y >= _height) ||            // Clip bottom
        ((x + 6 * size - 1) < 0) ||  // Clip left
        ((y + 8 * size - 1) < 0))    // Clip top
        return;

    if (c < 32) return;
#ifdef LOAD_GLCD
//>>>>>>>>>>>>>>>>>>
#ifdef LOAD_GFXFF
    if (!gfxFont) {  // 'Classic' built-in font
#endif
        //>>>>>>>>>>>>>>>>>>

        boolean fillbg = (bg != color);

        if ((size == 1) && fillbg) {
            uint8_t column[6];
            uint8_t mask = 0x1;
            spi_begin();

            setWindow(x, y, x + 5, y + 8);

            for (int8_t i = 0; i < 5; i++)
                column[i] = pgm_read_byte(font + (c * 5) + i);
            column[5] = 0;

#if defined(ESP8266) && !defined(ILI9488_DRIVER)
            color = (color >> 8) | (color << 8);
            bg    = (bg >> 8) | (bg << 8);

            for (int8_t j = 0; j < 8; j++) {
                for (int8_t k = 0; k < 5; k++) {
                    if (column[k] & mask) {
                        SPI1W0 = color;
                    } else {
                        SPI1W0 = bg;
                    }
                    SPI1CMD |= SPIBUSY;
                    while (SPI1CMD & SPIBUSY) {
                    }
                }

                mask <<= 1;

                SPI1W0 = bg;
                SPI1CMD |= SPIBUSY;
                while (SPI1CMD & SPIBUSY) {
                }
            }
#else  // for ESP32 or ILI9488

        for (int8_t j = 0; j < 8; j++) {
            for (int8_t k = 0; k < 5; k++) {
                if (column[k] & mask) {
                    tft_Write_16(color);
                } else {
                    tft_Write_16(bg);
                }
            }
            mask <<= 1;
            tft_Write_16(bg);
        }

#endif

            spi_end();
        } else {
            spi_begin();
            inTransaction = true;
            for (int8_t i = 0; i < 6; i++) {
                uint8_t line;
                if (i == 5)
                    line = 0x0;
                else
                    line = pgm_read_byte(font + (c * 5) + i);

                if (size == 1)  // default size
                {
                    for (int8_t j = 0; j < 8; j++) {
                        if (line & 0x1) drawPixel(x + i, y + j, color);
                        line >>= 1;
                    }
                } else {  // big size
                    for (int8_t j = 0; j < 8; j++) {
                        if (line & 0x1)
                            fillRect(x + (i * size), y + (j * size), size, size,
                                     color);
                        else if (fillbg)
                            fillRect(x + i * size, y + j * size, size, size,
                                     bg);
                        line >>= 1;
                    }
                }
            }
            inTransaction = false;
            spi_end();
        }

//>>>>>>>>>>>>>>>>>>>>>>>>>>>
#ifdef LOAD_GFXFF
    } else {  // Custom font
#endif
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
#endif  // LOAD_GLCD

#ifdef LOAD_GFXFF
        // Filter out bad characters not present in font
        if ((c >= pgm_read_word(&gfxFont->first)) &&
            (c <= pgm_read_word(&gfxFont->last))) {
            spi_begin();
            inTransaction = true;
            //>>>>>>>>>>>>>>>>>>>>>>>>>>>

            c -= pgm_read_word(&gfxFont->first);
            GFXglyph *glyph =
                &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
            uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap);

            uint32_t bo = pgm_read_word(&glyph->bitmapOffset);
            uint8_t w   = pgm_read_byte(&glyph->width),
                    h   = pgm_read_byte(&glyph->height);
            // xa = pgm_read_byte(&glyph->xAdvance);
            int8_t xo = pgm_read_byte(&glyph->xOffset),
                   yo = pgm_read_byte(&glyph->yOffset);
            uint8_t xx, yy, bits = 0, bit = 0;
            int16_t xo16 = 0, yo16 = 0;

            if (size > 1) {
                xo16 = xo;
                yo16 = yo;
            }

            // Here we have 3 versions of the same function just for evaluation
            // purposes Comment out the next two #defines to revert to the
            // slower Adafruit implementation

            // If FAST_LINE is defined then the free fonts are rendered using
            // horizontal lines this makes rendering fonts 2-5 times faster.
            // Particularly good for large fonts. This is an elegant solution
            // since it still uses generic functions present in the stock
            // library.

            // If FAST_SHIFT is defined then a slightly faster (at least for AVR
            // processors) shifting bit mask is used

            // Free fonts don't look good when the size multiplier is >1 so we
            // could remove code if this is not wanted and speed things up

#define FAST_HLINE
#define FAST_SHIFT
            // FIXED_SIZE is an option in User_Setup.h that only works with
            // FAST_LINE enabled

#ifdef FIXED_SIZE
            x += xo;  // Save 88 bytes of FLASH
            y += yo;
#endif

#ifdef FAST_HLINE

#ifdef FAST_SHIFT
            uint16_t hpc = 0;  // Horizontal foreground pixel count
            for (yy = 0; yy < h; yy++) {
                for (xx = 0; xx < w; xx++) {
                    if (bit == 0) {
                        bits = pgm_read_byte(&bitmap[bo++]);
                        bit  = 0x80;
                    }
                    if (bits & bit)
                        hpc++;
                    else {
                        if (hpc) {
#ifndef FIXED_SIZE
                            if (size == 1)
                                drawFastHLine(x + xo + xx - hpc, y + yo + yy,
                                              hpc, color);
                            else
                                fillRect(x + (xo16 + xx - hpc) * size,
                                         y + (yo16 + yy) * size, size * hpc,
                                         size, color);
#else
                            drawFastHLine(x + xx - hpc, y + yy, hpc, color);
#endif
                            hpc = 0;
                        }
                    }
                    bit >>= 1;
                }
                // Draw pixels for this line as we are about to increment yy
                if (hpc) {
#ifndef FIXED_SIZE
                    if (size == 1)
                        drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc,
                                      color);
                    else
                        fillRect(x + (xo16 + xx - hpc) * size,
                                 y + (yo16 + yy) * size, size * hpc, size,
                                 color);
#else
                    drawFastHLine(x + xx - hpc, y + yy, hpc, color);
#endif
                    hpc = 0;
                }
            }
#else
            uint16_t hpc = 0;  // Horizontal foreground pixel count
            for (yy = 0; yy < h; yy++) {
                for (xx = 0; xx < w; xx++) {
                    if (!(bit++ & 7)) {
                        bits = pgm_read_byte(&bitmap[bo++]);
                    }
                    if (bits & 0x80)
                        hpc++;
                    else {
                        if (hpc) {
                            if (size == 1)
                                drawFastHLine(x + xo + xx - hpc, y + yo + yy,
                                              hpc, color);
                            else
                                fillRect(x + (xo16 + xx - hpc) * size,
                                         y + (yo16 + yy) * size, size * hpc,
                                         size, color);
                            hpc = 0;
                        }
                    }
                    bits <<= 1;
                }
                // Draw pixels for this line as we are about to increment yy
                if (hpc) {
                    if (size == 1)
                        drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc,
                                      color);
                    else
                        fillRect(x + (xo16 + xx - hpc) * size,
                                 y + (yo16 + yy) * size, size * hpc, size,
                                 color);
                    hpc = 0;
                }
            }
#endif

#else
        for (yy = 0; yy < h; yy++) {
            for (xx = 0; xx < w; xx++) {
                if (!(bit++ & 7)) {
                    bits = pgm_read_byte(&bitmap[bo++]);
                }
                if (bits & 0x80) {
                    if (size == 1) {
                        drawPixel(x + xo + xx, y + yo + yy, color);
                    } else {
                        fillRect(x + (xo16 + xx) * size, y + (yo16 + yy) * size,
                                 size, size, color);
                    }
                }
                bits <<= 1;
            }
        }
#endif
            inTransaction = false;
            spi_end();
        }
#endif

#ifdef LOAD_GLCD
#ifdef LOAD_GFXFF
    }  // End classic vs custom font
#endif
#endif
}

/***************************************************************************************
** Function name:           setAddrWindow
** Description:             define an area to receive a stream of pixels
***************************************************************************************/
// Chip select is high at the end of this function
void TFT_eSPI::setAddrWindow(int32_t x0, int32_t y0, int32_t w, int32_t h) {
    spi_begin();

    setWindow(x0, y0, x0 + w - 1, y0 + h - 1);

    spi_end();
}

/***************************************************************************************
** Function name:           setWindow
** Description:             define an area to receive a stream of pixels
***************************************************************************************/
// Chip select stays low, call spi_begin first. Use setAddrWindow() from
// sketches
#if defined(ESP8266) && !defined(RPI_WRITE_STROBE) && \
    !defined(RPI_ILI9486_DRIVER)
void TFT_eSPI::setWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye) {
    // spi_begin(); // Must be called before setWimdow

#ifdef CGRAM_OFFSET
    xs += colstart;
    xe += colstart;
    ys += rowstart;
    ye += rowstart;
#endif

    // Column addr set
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

    SPI1W0 = TFT_CASET;
    SPI1CMD |= SPIBUSY;

    addr_col = 0xFFFF;
    addr_row = 0xFFFF;

    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
    // Load the two coords as a 32 bit value and shift in one go
    SPI1W0 = (xs >> 8) | (uint16_t)(xs << 8) |
             ((uint8_t)(xe >> 8) << 16 | (xe << 24));
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    // Row addr set
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

    SPI1W0 = TFT_PASET;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
    // Load the two coords as a 32 bit value and shift in one go
    SPI1W0 = (ys >> 8) | (uint16_t)(ys << 8) |
             ((uint8_t)(ye >> 8) << 16 | (ye << 24));
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    // write to RAM
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
    SPI1W0 = TFT_RAMWR;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    SPI1U1 = (15 << SPILMOSI) | (15 << SPILMISO);
    // spi_end();
}

#elif defined(ESP8266) && !defined(RPI_WRITE_STROBE) && \
    defined(                                            \
        RPI_ILI9486_DRIVER)  // This is for the RPi display that needs 16 bits

void TFT_eSPI::setWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye) {
    // spi_begin(); // Must be called before setWimdow

    addr_col = 0xFFFF;
    addr_row = 0xFFFF;

    // Column addr set
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

    SPI1W0 = TFT_CASET << 8;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    uint8_t xb[] = {
        0, (uint8_t)(xs >> 8), 0, (uint8_t)(xs >> 0),
        0, (uint8_t)(xe >> 8), 0, (uint8_t)(xe >> 0),
    };
    spi.writePattern(&xb[0], 8, 1);

    // Row addr set
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

    SPI1W0 = TFT_PASET << 8;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    uint8_t yb[] = {
        0, (uint8_t)(ys >> 8), 0, (uint8_t)(ys >> 0),
        0, (uint8_t)(ye >> 8), 0, (uint8_t)(ye >> 0),
    };
    spi.writePattern(&yb[0], 8, 1);

    // write to RAM
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
    SPI1W0 = TFT_RAMWR << 8;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    // Re-instate SPI flags settings corrupted by SPI library writePattern()
    // call
    SPI1U = SPI1U_WRITE;

    // spi_end();
}

#else

#if defined(ESP8266) && \
    defined(            \
        RPI_ILI9486_DRIVER)  // This is for the RPi display that needs 16 bits
void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
    // spi_begin(); // Must be called before setWimdow

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

    // Column addr set
    DC_C;

    SPI1W0 = TFT_CASET << (CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    addr_col = 0xFFFF;  // Use the waiting time to do something useful
    addr_row = 0xFFFF;
    while (SPI1CMD & SPIBUSY) {
    }
    DC_D;

    SPI1W0 = x0 >> 0;
    SPI1CMD |= SPIBUSY;
    x0 = x0 << 8;  // Use the waiting time to do something useful
    while (SPI1CMD & SPIBUSY) {
    }

    SPI1W0 = x0;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    SPI1W0 = x1 >> 0;
    SPI1CMD |= SPIBUSY;
    x1 = x1 << 8;  // Use the waiting time to do something useful
    while (SPI1CMD & SPIBUSY) {
    }

    SPI1W0 = x1;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    // Row addr set
    DC_C;

    SPI1W0 = TFT_PASET << (CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }
    DC_D;

    SPI1W0 = y0 >> 0;
    SPI1CMD |= SPIBUSY;
    y0 = y0 << 8;  // Use the waiting time to do something useful
    while (SPI1CMD & SPIBUSY) {
    }

    SPI1W0 = y0;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    SPI1W0 = y1 >> 0;
    SPI1CMD |= SPIBUSY;
    y1 = y1 << 8;  // Use the waiting time to do something useful
    while (SPI1CMD & SPIBUSY) {
    }

    SPI1W0 = y1;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    // write to RAM
    DC_C;

    SPI1W0 = TFT_RAMWR << (CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    // spi_end();
}

#else  // This is for the ESP32

void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
    // spi_begin(); // Must be called before setWimdow

    addr_col = 0xFFFF;
    addr_row = 0xFFFF;

#ifdef CGRAM_OFFSET
    x0 += colstart;
    x1 += colstart;
    y0 += rowstart;
    y1 += rowstart;
#endif

    DC_C;

    tft_Write_8(TFT_CASET);

    DC_D;

#if defined(RPI_ILI9486_DRIVER)
    uint8_t xb[] = {
        0, (uint8_t)(x0 >> 8), 0, (uint8_t)(x0 >> 0),
        0, (uint8_t)(x1 >> 8), 0, (uint8_t)(x1 >> 0),
    };
    spi.writePattern(&xb[0], 8, 1);
#else
    tft_Write_32(SPI_32(x0, x1));
#endif

    DC_C;

    // Row addr set
    tft_Write_8(TFT_PASET);

    DC_D;

#if defined(RPI_ILI9486_DRIVER)
    uint8_t yb[] = {
        0, (uint8_t)(y0 >> 8), 0, (uint8_t)(y0 >> 0),
        0, (uint8_t)(y1 >> 8), 0, (uint8_t)(y1 >> 0),
    };
    spi.writePattern(&yb[0], 8, 1);
#else
    tft_Write_32(SPI_32(y0, y1));
#endif

    DC_C;

    // write to RAM
    tft_Write_8(TFT_RAMWR);

    DC_D;

    // spi_end();
}
#endif  // end RPI_ILI9486_DRIVER check
#endif  // end ESP32 check

/***************************************************************************************
** Function name:           readAddrWindow
** Description:             define an area to read a stream of pixels
***************************************************************************************/
// Chip select stays low
#if defined(ESP8266) && !defined(RPI_WRITE_STROBE)
void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h) {
    int32_t xe = xs + w - 1;
    int32_t ye = ys + h - 1;

    addr_col = 0xFFFF;
    addr_row = 0xFFFF;

#ifdef CGRAM_OFFSET
    xs += colstart;
    xe += colstart;
    ys += rowstart;
    ye += rowstart;
#endif

    // Column addr set
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

    SPI1W0 = TFT_CASET;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
    // Load the two coords as a 32 bit value and shift in one go
    SPI1W0 = (xs >> 8) | (uint16_t)(xs << 8) |
             ((uint8_t)(xe >> 8) << 16 | (xe << 24));
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    // Row addr set
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

    SPI1W0 = TFT_PASET;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
    // Load the two coords as a 32 bit value and shift in one go
    SPI1W0 = (ys >> 8) | (uint16_t)(ys << 8) |
             ((uint8_t)(ye >> 8) << 16 | (ye << 24));
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    // read from RAM
    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
    SPI1W0 = TFT_RAMRD;
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;
}

#else  // ESP32

void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h) {
    int32_t xe = xs + w - 1;
    int32_t ye = ys + h - 1;

    addr_col = 0xFFFF;
    addr_row = 0xFFFF;

#ifdef CGRAM_OFFSET
    xs += colstart;
    xe += colstart;
    ys += rowstart;
    ye += rowstart;
#endif

    // Column addr set
    DC_C;

    tft_Write_8(TFT_CASET);

    DC_D;

    tft_Write_32(SPI_32(xs, xe));

    // Row addr set
    DC_C;

    tft_Write_8(TFT_PASET);

    DC_D;

    tft_Write_32(SPI_32(ys, ye));

    DC_C;

    tft_Write_8(TFT_RAMRD);  // Read CGRAM command

    DC_D;
}

#endif

/***************************************************************************************
** Function name:           drawPixel
** Description:             push a single pixel at an arbitrary position
***************************************************************************************/
#if defined(ESP8266) && !defined(RPI_WRITE_STROBE)
void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) {
    // Range checking
    if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) return;

#ifdef CGRAM_OFFSET
    x += colstart;
    y += rowstart;
#endif

    spi_begin();

    // No need to send x if it has not changed (speeds things up)
    if (addr_col != x) {
        DC_C;

        SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
        SPI1W0 = TFT_CASET << (CMD_BITS + 1 - 8);
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        DC_D;

#if defined(RPI_ILI9486_DRIVER)  // This is for the RPi display that needs 16
                                 // bits per byte
        uint8_t cBin[] = {0, (uint8_t)(x >> 8), 0, (uint8_t)(x >> 0)};
        spi.writePattern(&cBin[0], 4, 2);
#else
        SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
        // Load the two coords as a 32 bit value and shift in one go
        uint32_t xswap = (x >> 8) | (uint16_t)(x << 8);
        SPI1W0         = xswap | (xswap << 16);
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }
#endif

        addr_col = x;
    }

    // No need to send y if it has not changed (speeds things up)
    if (addr_row != y) {
        DC_C;

        SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

        SPI1W0 = TFT_PASET << (CMD_BITS + 1 - 8);
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        DC_D;

#if defined(RPI_ILI9486_DRIVER)  // This is for the RPi display that needs 16
                                 // bits per byte
        uint8_t cBin[] = {0, (uint8_t)(y >> 8), 0, (uint8_t)(y >> 0)};
        spi.writePattern(&cBin[0], 4, 2);
#else
        SPI1U1 = (31 << SPILMOSI) | (31 << SPILMISO);
        // Load the two coords as a 32 bit value and shift in one go
        uint32_t yswap = (y >> 8) | (uint16_t)(y << 8);
        SPI1W0         = yswap | (yswap << 16);
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }
#endif

        addr_row = y;
    }

    DC_C;

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);

    SPI1W0 = TFT_RAMWR << (CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

#if defined(ILI9488_DRIVER)
    tft_Write_16(color);
#else
    SPI1U1 = (15 << SPILMOSI) | (15 << SPILMISO);

    SPI1W0 = (color >> 8) | (color << 8);
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }
#endif

    spi_end();
}

#else

#if defined(ESP8266) && \
    defined(            \
        RPI_ILI9486_DRIVER)  // This is for the RPi display that needs 16 bits

void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) {
    // Range checking
    if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) return;

    spi_begin();

    SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
    // No need to send x if it has not changed (speeds things up)
    if (addr_col != x) {
        DC_C;

        SPI1W0 = TFT_CASET << (CMD_BITS + 1 - 8);
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }
        DC_D;

        SPI1W0 = x >> 0;
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        SPI1W0 = x << 8;
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        SPI1W0 = x >> 0;
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        SPI1W0 = x << 8;
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        addr_col = x;
    }

    // No need to send y if it has not changed (speeds things up)
    if (addr_row != y) {
        DC_C;

        SPI1W0 = TFT_PASET << (CMD_BITS + 1 - 8);
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }
        DC_D;

        SPI1W0 = y >> 0;
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        SPI1W0 = y << 8;
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        SPI1W0 = y >> 0;
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        SPI1W0 = y << 8;
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }

        addr_row = y;
    }

    DC_C;

    SPI1W0 = TFT_RAMWR << (CMD_BITS + 1 - 8);
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    DC_D;

    SPI1W0 = (color >> 8) | (color << 8);
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }

    spi_end();
}

#else  // ESP32

void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) {
    // Range checking
    if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) return;

    spi_begin();

#ifdef CGRAM_OFFSET
    x += colstart;
    y += rowstart;
#endif

    DC_C;

    // No need to send x if it has not changed (speeds things up)
    if (addr_col != x) {
        tft_Write_8(TFT_CASET);

        DC_D;

#if defined(RPI_ILI9486_DRIVER)
        uint8_t xb[] = {
            0, (uint8_t)(x >> 8), 0, (uint8_t)(x >> 0),
            0, (uint8_t)(x >> 8), 0, (uint8_t)(x >> 0),
        };
        spi.writePattern(&xb[0], 8, 1);
#else
        tft_Write_32(SPI_32(x, x));
#endif

        DC_C;

        addr_col = x;
    }

    // No need to send y if it has not changed (speeds things up)
    if (addr_row != y) {
        tft_Write_8(TFT_PASET);

        DC_D;

#if defined(RPI_ILI9486_DRIVER)
        uint8_t yb[] = {
            0, (uint8_t)(y >> 8), 0, (uint8_t)(y >> 0),
            0, (uint8_t)(y >> 8), 0, (uint8_t)(y >> 0),
        };
        spi.writePattern(&yb[0], 8, 1);
#else
        tft_Write_32(SPI_32(y, y));
#endif

        DC_C;

        addr_row = y;
    }

    tft_Write_8(TFT_RAMWR);

    DC_D;

    tft_Write_16(color);

    spi_end();
}
#endif
#endif

/***************************************************************************************
** Function name:           pushColor
** Description:             push a single pixel
***************************************************************************************/
void TFT_eSPI::pushColor(uint16_t color) {
    spi_begin();

    tft_Write_16(color);

    spi_end();
}

/***************************************************************************************
** Function name:           pushColor
** Description:             push a single colour to "len" pixels
***************************************************************************************/
void TFT_eSPI::pushColor(uint16_t color, uint32_t len) {
    spi_begin();

#ifdef RPI_WRITE_STROBE
    uint8_t colorBin[] = {(uint8_t)(color >> 8), (uint8_t)color};
    if (len) spi.writePattern(&colorBin[0], 2, 1);
    len--;
    while (len--) {
        WR_L;
        WR_H;
    }
#else
#if defined(ESP32_PARALLEL)
    while (len--) {
        tft_Write_16(color);
    }
#else
    writeBlock(color, len);
#endif
#endif

    spi_end();
}

/***************************************************************************************
** Function name:           startWrite
** Description:             begin transaction with CS low, MUST later call
*endWrite
***************************************************************************************/
void TFT_eSPI::startWrite(void) {
    spi_begin();
    inTransaction = true;
}

/***************************************************************************************
** Function name:           endWrite
** Description:             end transaction with CS high
***************************************************************************************/
void TFT_eSPI::endWrite(void) {
    inTransaction = false;
    spi_end();
}

/***************************************************************************************
** Function name:           writeColor (use startWrite() and endWrite() before &
*after)
** Description:             raw write of "len" pixels avoiding transaction check
***************************************************************************************/
void TFT_eSPI::writeColor(uint16_t color, uint32_t len) {
#ifdef RPI_WRITE_STROBE
    uint8_t colorBin[] = {(uint8_t)(color >> 8), (uint8_t)color};
    if (len) spi.writePattern(&colorBin[0], 2, 1);
    len--;
    while (len--) {
        WR_L;
        WR_H;
    }
#else
#if defined(ESP32_PARALLEL)
    while (len--) {
        tft_Write_16(color);
    }
#else
    writeBlock(color, len);
#endif
#endif
}

/***************************************************************************************
** Function name:           pushColors
** Description:             push an array of pixels for 16 bit raw image drawing
***************************************************************************************/
// Assumed that setAddrWindow() has previously been called

void TFT_eSPI::pushColors(uint8_t *data, uint32_t len) {
    spi_begin();

#if defined(RPI_WRITE_STROBE)
    while (len >= 64) {
        spi.writePattern(data, 64, 1);
        data += 64;
        len -= 64;
    }
    if (len) spi.writePattern(data, len, 1);
#else
#ifdef ESP32_PARALLEL
    while (len--) {
        tft_Write_8(*data);
        data++;
    }
#elif defined(ILI9488_DRIVER)
    uint16_t color;
    while (len > 1) {
        color = (*data++) | ((*data++) << 8);
        tft_Write_16(color);
        len -= 2;
    }
#else
#if (SPI_FREQUENCY == 80000000)
    while (len >= 64) {
        spi.writePattern(data, 64, 1);
        data += 64;
        len -= 64;
    }
    if (len) spi.writePattern(data, len, 1);
#else
    spi.writeBytes(data, len);
#endif
#endif
#endif

    spi_end();
}

/***************************************************************************************
** Function name:           pushColors
** Description:             push an array of pixels, for image drawing
***************************************************************************************/
void TFT_eSPI::pushColors(uint16_t *data, uint32_t len, bool swap) {
    spi_begin();

#if defined(ESP32) || defined(ILI9488_DRIVER)
#if defined(ESP32_PARALLEL) || defined(ILI9488_DRIVER)
    if (swap)
        while (len--) {
            tft_Write_16(*data);
            data++;
        }
    else
        while (len--) {
            tft_Write_16S(*data);
            data++;
        }
#else
    if (swap)
        spi.writePixels(data, len << 1);
    else
        spi.writeBytes((uint8_t *)data, len << 1);
#endif
#else

    uint32_t color[8];

    SPI1U1 = (255 << SPILMOSI) | (255 << SPILMISO);

    while (len > 15) {
        if (swap) {
            uint32_t i = 0;
            while (i < 8) {
                color[i] = (*data >> 8) | (uint16_t)(*data << 8);
                data++;
                color[i] |= ((*data >> 8) | (*data << 8)) << 16;
                data++;
                i++;
            }
        } else {
            memcpy(color, data, 32);
            data += 16;
        }

        len -= 16;

        // ESP8266 wait time here at 40MHz SPI is ~5.45us
        while (SPI1CMD & SPIBUSY) {
        }
        SPI1W0 = color[0];
        SPI1W1 = color[1];
        SPI1W2 = color[2];
        SPI1W3 = color[3];
        SPI1W4 = color[4];
        SPI1W5 = color[5];
        SPI1W6 = color[6];
        SPI1W7 = color[7];
        SPI1CMD |= SPIBUSY;
    }

    if (len) {
        uint32_t bits = (len * 16 - 1);  // bits left to shift - 1
        if (swap) {
            uint16_t *ptr = (uint16_t *)color;
            while (len--) {
                *ptr++ = (*(data) >> 8) | (uint16_t)(*(data) << 8);
                data++;
            }
        } else {
            memcpy(color, data, len << 1);
        }
        while (SPI1CMD & SPIBUSY) {
        }
        SPI1U1 = (bits << SPILMOSI) | (bits << SPILMISO);
        SPI1W0 = color[0];
        SPI1W1 = color[1];
        SPI1W2 = color[2];
        SPI1W3 = color[3];
        SPI1W4 = color[4];
        SPI1W5 = color[5];
        SPI1W6 = color[6];
        SPI1W7 = color[7];
        SPI1CMD |= SPIBUSY;
    }

    while (SPI1CMD & SPIBUSY) {
    }

#endif

    spi_end();
}

/***************************************************************************************
** Function name:           drawLine
** Description:             draw a line between 2 arbitrary points
***************************************************************************************/
// Bresenham's algorithm - thx wikipedia - speed enhanced by Bodmer to use
// an efficient FastH/V Line draw routine for line segments of 2 pixels or more

#if defined(RPI_ILI9486_DRIVER) || defined(ESP32) ||        \
    defined(RPI_WRITE_STROBE) || defined(HX8357D_DRIVER) || \
    defined(ILI9488_DRIVER)

void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1,
                        uint32_t color) {
    spi_begin();
    inTransaction = true;
    boolean steep = abs(y1 - y0) > abs(x1 - x0);
    if (steep) {
        swap_coord(x0, y0);
        swap_coord(x1, y1);
    }

    if (x0 > x1) {
        swap_coord(x0, x1);
        swap_coord(y0, y1);
    }

    int32_t dx = x1 - x0, dy = abs(y1 - y0);
    ;

    int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;

    if (y0 < y1) ystep = 1;

    // Split into steep and not steep for FastH/V separation
    if (steep) {
        for (; x0 <= x1; x0++) {
            dlen++;
            err -= dy;
            if (err < 0) {
                err += dx;
                if (dlen == 1)
                    drawPixel(y0, xs, color);
                else
                    drawFastVLine(y0, xs, dlen, color);
                dlen = 0;
                y0 += ystep;
                xs = x0 + 1;
            }
        }
        if (dlen) drawFastVLine(y0, xs, dlen, color);
    } else {
        for (; x0 <= x1; x0++) {
            dlen++;
            err -= dy;
            if (err < 0) {
                err += dx;
                if (dlen == 1)
                    drawPixel(xs, y0, color);
                else
                    drawFastHLine(xs, y0, dlen, color);
                dlen = 0;
                y0 += ystep;
                xs = x0 + 1;
            }
        }
        if (dlen) drawFastHLine(xs, y0, dlen, color);
    }
    inTransaction = false;
    spi_end();
}

#else

// This is a weeny bit faster
void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1,
                        uint32_t color) {
    boolean steep = abs(y1 - y0) > abs(x1 - x0);

    if (steep) {
        swap_coord(x0, y0);
        swap_coord(x1, y1);
    }

    if (x0 > x1) {
        swap_coord(x0, x1);
        swap_coord(y0, y1);
    }

    if (x1 < 0) return;

    int16_t dx, dy;
    dx = x1 - x0;
    dy = abs(y1 - y0);

    int16_t err  = dx / 2;
    int8_t ystep = (y0 < y1) ? 1 : (-1);

    spi_begin();

    int16_t swapped_color = (color >> 8) | (color << 8);

    if (steep)  // y increments every iteration (y0 is x-axis, and x0 is y-axis)
    {
        if (x1 >= (int32_t)_height) x1 = _height - 1;

        for (; x0 <= x1; x0++) {
            if ((x0 >= 0) && (y0 >= 0) && (y0 < _width)) break;
            err -= dy;
            if (err < 0) {
                err += dx;
                y0 += ystep;
            }
        }

        if (x0 > x1) {
            spi_end();
            return;
        }

        setWindow(y0, x0, y0, _height);
        SPI1W0 = swapped_color;
        for (; x0 <= x1; x0++) {
            while (SPI1CMD & SPIBUSY) {
            }
            SPI1CMD |= SPIBUSY;

            err -= dy;
            if (err < 0) {
                y0 += ystep;
                if ((y0 < 0) || (y0 >= _width)) break;
                err += dx;
                while (SPI1CMD & SPIBUSY) {
                }
                setWindow(y0, x0 + 1, y0, _height);
                SPI1W0 = swapped_color;
            }
        }
    } else  // x increments every iteration (x0 is x-axis, and y0 is y-axis)
    {
        if (x1 >= _width) x1 = _width - 1;

        for (; x0 <= x1; x0++) {
            if ((x0 >= 0) && (y0 >= 0) && (y0 < (int32_t)_height)) break;
            err -= dy;
            if (err < 0) {
                err += dx;
                y0 += ystep;
            }
        }

        if (x0 > x1) {
            spi_end();
            return;
        }

        setWindow(x0, y0, _width, y0);
        SPI1W0 = swapped_color;
        for (; x0 <= x1; x0++) {
            while (SPI1CMD & SPIBUSY) {
            }
            SPI1CMD |= SPIBUSY;

            err -= dy;
            if (err < 0) {
                y0 += ystep;
                if ((y0 < 0) || (y0 >= (int32_t)_height)) break;
                err += dx;
                while (SPI1CMD & SPIBUSY) {
                }
                setWindow(x0 + 1, y0, _width, y0);
                SPI1W0 = swapped_color;
            }
        }
    }

    while (SPI1CMD & SPIBUSY) {
    }

    spi_end();
}

#endif

/***************************************************************************************
** Function name:           drawFastVLine
** Description:             draw a vertical line
***************************************************************************************/
#if defined(ESP8266) && !defined(RPI_WRITE_STROBE)
void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) {
    // Clipping
    if ((x < 0) || (x >= _width) || (y >= _height)) return;

    if (y < 0) {
        h += y;
        y = 0;
    }

    if ((y + h) > _height) h = _height - y;

    if (h < 1) return;

    spi_begin();

    setWindow(x, y, x, y + h - 1);

    writeBlock(color, h);

    spi_end();
}

#else

void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) {
    // Clipping
    if ((x < 0) || (x >= _width) || (y >= _height)) return;

    if (y < 0) {
        h += y;
        y = 0;
    }

    if ((y + h) > _height) h = _height - y;

    if (h < 1) return;

    spi_begin();

    setWindow(x, y, x, y + h - 1);

#ifdef RPI_WRITE_STROBE
#if defined(ESP8266)
    SPI1W0 = (color >> 8) | (color << 8);
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }
#else
    tft_Write_16(color);
#endif
    h--;
    while (h--) {
        WR_L;
        WR_H;
    }
#else
#ifdef ESP32_PARALLEL
    while (h--) {
        tft_Write_16(color);
    }
#else
    writeBlock(color, h);
#endif
#endif

    spi_end();
}
#endif

/***************************************************************************************
** Function name:           drawFastHLine
** Description:             draw a horizontal line
***************************************************************************************/
#if defined(ESP8266) && !defined(RPI_WRITE_STROBE)
void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) {
    // Clipping
    if ((y < 0) || (x >= _width) || (y >= _height)) return;

    if (x < 0) {
        w += x;
        x = 0;
    }

    if ((x + w) > _width) w = _width - x;

    if (w < 1) return;

    spi_begin();

    setWindow(x, y, x + w - 1, y);

    writeBlock(color, w);

    spi_end();
}

#else

void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) {
    // Clipping
    if ((y < 0) || (x >= _width) || (y >= _height)) return;

    if (x < 0) {
        w += x;
        x = 0;
    }

    if ((x + w) > _width) w = _width - x;

    if (w < 1) return;

    spi_begin();

    setWindow(x, y, x + w - 1, y);

#ifdef RPI_WRITE_STROBE
#if defined(ESP8266)
    SPI1W0 = (color >> 8) | (color << 8);
    SPI1CMD |= SPIBUSY;
    while (SPI1CMD & SPIBUSY) {
    }
#else
    tft_Write_16(color);
#endif
    w--;
    while (w--) {
        WR_L;
        WR_H;
    }
#else
#ifdef ESP32_PARALLEL
    while (w--) {
        tft_Write_16(color);
    }
#else
    writeBlock(color, w);
#endif
#endif

    spi_end();
}
#endif

/***************************************************************************************
** Function name:           fillRect
** Description:             draw a filled rectangle
***************************************************************************************/
#if defined(ESP8266) && !defined(RPI_WRITE_STROBE)
void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h,
                        uint32_t color) {
    // Clipping
    if ((x >= _width) || (y >= _height)) return;

    if (x < 0) {
        w += x;
        x = 0;
    }
    if (y < 0) {
        h += y;
        y = 0;
    }

    if ((x + w) > _width) w = _width - x;
    if ((y + h) > _height) h = _height - y;

    if ((w < 1) || (h < 1)) return;

    spi_begin();

    setWindow(x, y, x + w - 1, y + h - 1);

    writeBlock(color, w * h);

    spi_end();
}

#else

void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h,
                        uint32_t color) {
    // Clipping
    if ((x >= _width) || (y >= _height)) return;

    if (x < 0) {
        w += x;
        x = 0;
    }
    if (y < 0) {
        h += y;
        y = 0;
    }

    if ((x + w) > _width) w = _width - x;
    if ((y + h) > _height) h = _height - y;

    if ((w < 1) || (h < 1)) return;

    spi_begin();

    setWindow(x, y, x + w - 1, y + h - 1);

    uint32_t n = (uint32_t)w * (uint32_t)h;

#ifdef RPI_WRITE_STROBE
    tft_Write_16(color);
    while (n--) {
        WR_L;
        WR_H;
    }
#else
#ifdef ESP32_PARALLEL
    if (color >> 8 == (uint8_t)color) {
        tft_Write_8(color);
        n--;
        WR_L;
        WR_H;
        while (n) {
            WR_L;
            WR_H;
            n--;
            WR_L;
            WR_H;
        }
    } else {
        while (n--) {
            tft_Write_16(color);
        }
    }
#else
    writeBlock(color, n);
#endif
#endif

    spi_end();
}
#endif

/***************************************************************************************
** Function name:           color565
** Description:             convert three 8 bit RGB levels to a 16 bit colour
*value
***************************************************************************************/
uint16_t TFT_eSPI::color565(uint8_t r, uint8_t g, uint8_t b) {
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}

/***************************************************************************************
** Function name:           color16to8
** Description:             convert 16 bit colour to an 8 bit 332 RGB colour
*value
***************************************************************************************/
uint8_t TFT_eSPI::color16to8(uint16_t c) {
    return ((c & 0xE000) >> 8) | ((c & 0x0700) >> 6) | ((c & 0x0018) >> 3);
}

/***************************************************************************************
** Function name:           color8to16
** Description:             convert 8 bit colour to a 16 bit 565 colour value
***************************************************************************************/
uint16_t TFT_eSPI::color8to16(uint8_t color) {
    uint8_t blue[]   = {0, 11, 21, 31};  // blue 2 to 5 bit colour lookup table
    uint16_t color16 = 0;

    //        =====Green=====     ===============Red==============
    color16 = (color & 0x1C) << 6 | (color & 0xC0) << 5 | (color & 0xE0) << 8;
    //        =====Green=====    =======Blue======
    color16 |= (color & 0x1C) << 3 | blue[color & 0x03];

    return color16;
}

/***************************************************************************************
** Function name:           invertDisplay
** Description:             invert the display colours i = 1 invert, i = 0
*normal
***************************************************************************************/
void TFT_eSPI::invertDisplay(boolean i) {
    spi_begin();
    // Send the command twice as otherwise it does not always work!
    writecommand(i ? TFT_INVON : TFT_INVOFF);
    writecommand(i ? TFT_INVON : TFT_INVOFF);
    spi_end();
}

/**************************************************************************
** Function name:           setAttribute
** Description:             Sets a control parameter of an attribute
**************************************************************************/
void TFT_eSPI::setAttribute(uint8_t attr_id, uint8_t param) {
    switch (attr_id) {
        break;
        case 1:
            _cp437 = param;
            break;
        case 2:
            _utf8 = param;
            break;
            // case 3: // TBD future feature control
            //    _tbd = param;
            //    break;
    }
}

/**************************************************************************
** Function name:           getAttribute
** Description:             Get value of an attribute (control parameter)
**************************************************************************/
uint8_t TFT_eSPI::getAttribute(uint8_t attr_id) {
    switch (attr_id) {
        case 1:  // ON/OFF control of full CP437 character set
            return _cp437;
            break;
        case 2:  // ON/OFF control of UTF-8 decoding
            return _utf8;
            break;
            // case 3: // TBD future feature control
            //    return _tbd;
            //    break;
    }

    return false;
}

/***************************************************************************************
** Function name:           decodeUTF8
** Description:             Serial UTF-8 decoder with fall-back to extended
*ASCII
*************************************************************************************x*/
#define DECODE_UTF8  // Test only, comment out to stop decoding
uint16_t TFT_eSPI::decodeUTF8(uint8_t c) {
#ifdef DECODE_UTF8
    // 7 bit Unicode Code Point
    if ((c & 0x80) == 0x00) {
        decoderState = 0;
        return (uint16_t)c;
    }

    if (decoderState == 0) {
        // 11 bit Unicode Code Point
        if ((c & 0xE0) == 0xC0) {
            decoderBuffer = ((c & 0x1F) << 6);
            decoderState  = 1;
            return 0;
        }

        // 16 bit Unicode Code Point
        if ((c & 0xF0) == 0xE0) {
            decoderBuffer = ((c & 0x0F) << 12);
            decoderState  = 2;
            return 0;
        }
        // 21 bit Unicode  Code Point not supported so fall-back to extended
        // ASCII
        if ((c & 0xF8) == 0xF0) return (uint16_t)c;
    } else {
        if (decoderState == 2) {
            decoderBuffer |= ((c & 0x3F) << 6);
            decoderState--;
            return 0;
        } else {
            decoderBuffer |= (c & 0x3F);
            decoderState = 0;
            return decoderBuffer;
        }
    }

    decoderState = 0;
#endif

    return (uint16_t)c;  // fall-back to extended ASCII
}

/***************************************************************************************
** Function name:           decodeUTF8
** Description:             Line buffer UTF-8 decoder with fall-back to extended
*ASCII
*************************************************************************************x*/
uint16_t TFT_eSPI::decodeUTF8(uint8_t *buf, uint16_t *index,
                              uint16_t remaining) {
    byte c = buf[(*index)++];
    // Serial.print("Byte from string = 0x"); Serial.println(c, HEX);

#ifdef DECODE_UTF8
    // 7 bit Unicode
    if ((c & 0x80) == 0x00) return c;

    // 11 bit Unicode
    if (((c & 0xE0) == 0xC0) && (remaining > 1))
        return ((c & 0x1F) << 6) | (buf[(*index)++] & 0x3F);

    // 16 bit Unicode
    if (((c & 0xF0) == 0xE0) && (remaining > 2)) {
        c = ((c & 0x0F) << 12) | ((buf[(*index)++] & 0x3F) << 6);
        return c | ((buf[(*index)++] & 0x3F));
    }

    // 21 bit Unicode not supported so fall-back to extended ASCII
    // if ((c & 0xF8) == 0xF0) return c;
#endif

    return c;  // fall-back to extended ASCII
}

/***************************************************************************************
** Function name:           write
** Description:             draw characters piped through serial stream
***************************************************************************************/
size_t TFT_eSPI::write(uint8_t utf8) {
    if (utf8 == '\r') return 1;

    uint16_t uniCode = utf8;

    if (_utf8) uniCode = decodeUTF8(utf8);

    if (uniCode == 0) return 1;

#ifdef SMOOTH_FONT
    if (fontLoaded) {
        // Serial.print("UniCode="); Serial.println(uniCode);
        // Serial.print("UTF8   ="); Serial.println(utf8);

        // fontFile = SPIFFS.open( _gFontFilename, "r" );

        // if(!fontFile)
        //{
        //  fontLoaded = false;
        //  return 1;
        //}

        drawGlyph(uniCode);

        // fontFile.close();
        return 1;
    }
#endif

    if (uniCode == '\n')
        uniCode += 22;  // Make it a valid space character to stop errors
    else if (uniCode < 32)
        return 1;

    uint16_t width  = 0;
    uint16_t height = 0;

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv Serial.print((uint8_t) uniCode); //
// Debug line sends all printed TFT text to serial port Serial.println(uniCode,
// HEX); // Debug line sends all printed TFT text to serial port delay(5); //
// Debug optional wait for serial port to flush through
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#ifdef LOAD_GFXFF
    if (!gfxFont) {
#endif
        //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

#ifdef LOAD_FONT2
        if (textfont == 2) {
            if (uniCode > 127) return 1;

            width  = pgm_read_byte(widtbl_f16 + uniCode - 32);
            height = chr_hgt_f16;
            // Font 2 is rendered in whole byte widths so we must allow for this
            width =
                (width + 6) / 8;  // Width in whole bytes for font 2, should be
                                  // + 7 but must allow for font width change
            width = width * 8;    // Width converted back to pixels
        }
#ifdef LOAD_RLE
        else
#endif
#endif

#ifdef LOAD_RLE
        {
            if ((textfont > 2) && (textfont < 9)) {
                if (uniCode > 127) return 1;
                // Uses the fontinfo struct array to avoid lots of 'if' or
                // 'switch' statements
                width = pgm_read_byte(
                    (uint8_t *)pgm_read_dword(&(fontdata[textfont].widthtbl)) +
                    uniCode - 32);
                height = pgm_read_byte(&fontdata[textfont].height);
            }
        }
#endif

#ifdef LOAD_GLCD
        if (textfont == 1) {
            width  = 6;
            height = 8;
        }
#else
    if (textfont == 1) return 1;
#endif

        height = height * textsize;

        if (utf8 == '\n') {
            cursor_y += height;
            cursor_x = 0;
        } else {
            if (textwrapX && (cursor_x + width * textsize > _width)) {
                cursor_y += height;
                cursor_x = 0;
            }
            if (textwrapY && (cursor_y >= (int32_t)_height)) cursor_y = 0;
            cursor_x += drawChar(uniCode, cursor_x, cursor_y, textfont);
        }

//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#ifdef LOAD_GFXFF
    }  // Custom GFX font
    else {
        if (utf8 == '\n') {
            cursor_x = 0;
            cursor_y +=
                (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
        } else {
            if (uniCode > pgm_read_word(&gfxFont->last)) return 1;
            if (uniCode < pgm_read_word(&gfxFont->first)) return 1;

            uint16_t c2 = uniCode - pgm_read_word(&gfxFont->first);
            GFXglyph *glyph =
                &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
            uint8_t w = pgm_read_byte(&glyph->width),
                    h = pgm_read_byte(&glyph->height);
            if ((w > 0) && (h > 0)) {  // Is there an associated bitmap?
                int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset);
                if (textwrapX && ((cursor_x + textsize * (xo + w)) > _width)) {
                    // Drawing character would go off right edge; wrap to new
                    // line
                    cursor_x = 0;
                    cursor_y += (int16_t)textsize *
                                (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
                }
                if (textwrapY && (cursor_y >= (int32_t)_height)) cursor_y = 0;
                drawChar(cursor_x, cursor_y, uniCode, textcolor, textbgcolor,
                         textsize);
            }
            cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize;
        }
    }
#endif  // LOAD_GFXFF
        //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    return 1;
}

/***************************************************************************************
** Function name:           drawChar
** Description:             draw a Unicode glyph onto the screen
***************************************************************************************/
// Any UTF-8 decoding must be done before calling drawChar()
int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y) {
    return drawChar(uniCode, x, y, textfont);
}

// Any UTF-8 decoding must be done before calling drawChar()
int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y,
                           uint8_t font) {
    if (!uniCode) return 0;

    if (font == 1) {
#ifdef LOAD_GLCD
#ifndef LOAD_GFXFF
        drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
        return 6 * textsize;
#endif
#else
#ifndef LOAD_GFXFF
        return 0;
#endif
#endif

#ifdef LOAD_GFXFF
        drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
        if (!gfxFont) {  // 'Classic' built-in font
#ifdef LOAD_GLCD
            return 6 * textsize;
#else
            return 0;
#endif
        } else {
            if ((uniCode >= pgm_read_word(&gfxFont->first)) &&
                (uniCode <= pgm_read_word(&gfxFont->last))) {
                uint16_t c2 = uniCode - pgm_read_word(&gfxFont->first);
                GFXglyph *glyph =
                    &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
                return pgm_read_byte(&glyph->xAdvance) * textsize;
            } else {
                return 0;
            }
        }
#endif
    }

    if ((font > 1) && (font < 9) && ((uniCode < 32) || (uniCode > 127)))
        return 0;

    int32_t width          = 0;
    int32_t height         = 0;
    uint32_t flash_address = 0;
    uniCode -= 32;

#ifdef LOAD_FONT2
    if (font == 2) {
        flash_address = pgm_read_dword(&chrtbl_f16[uniCode]);
        width         = pgm_read_byte(widtbl_f16 + uniCode);
        height        = chr_hgt_f16;
    }
#ifdef LOAD_RLE
    else
#endif
#endif

#ifdef LOAD_RLE
    {
        if ((font > 2) && (font < 9)) {
            flash_address =
                pgm_read_dword(pgm_read_dword(&(fontdata[font].chartbl)) +
                               uniCode * sizeof(void *));
            width = pgm_read_byte(
                (uint8_t *)pgm_read_dword(&(fontdata[font].widthtbl)) +
                uniCode);
            height = pgm_read_byte(&fontdata[font].height);
        }
    }
#endif

    int32_t w    = width;
    int32_t pX   = 0;
    int32_t pY   = y;
    uint8_t line = 0;

#ifdef LOAD_FONT2  // chop out code if we do not need it
    if (font == 2) {
        w = w +
            6;  // Should be + 7 but we need to compensate for width increment
        w = w / 8;
        if (x + width * textsize >= (int16_t)_width) return width * textsize;

        if (textcolor == textbgcolor || textsize != 1) {
            spi_begin();
            inTransaction = true;

            for (int32_t i = 0; i < height; i++) {
                if (textcolor != textbgcolor)
                    fillRect(x, pY, width * textsize, textsize, textbgcolor);

                for (int32_t k = 0; k < w; k++) {
                    line = pgm_read_byte((uint8_t *)flash_address + w * i + k);
                    if (line) {
                        if (textsize == 1) {
                            pX = x + k * 8;
                            if (line & 0x80) drawPixel(pX, pY, textcolor);
                            if (line & 0x40) drawPixel(pX + 1, pY, textcolor);
                            if (line & 0x20) drawPixel(pX + 2, pY, textcolor);
                            if (line & 0x10) drawPixel(pX + 3, pY, textcolor);
                            if (line & 0x08) drawPixel(pX + 4, pY, textcolor);
                            if (line & 0x04) drawPixel(pX + 5, pY, textcolor);
                            if (line & 0x02) drawPixel(pX + 6, pY, textcolor);
                            if (line & 0x01) drawPixel(pX + 7, pY, textcolor);
                        } else {
                            pX = x + k * 8 * textsize;
                            if (line & 0x80)
                                fillRect(pX, pY, textsize, textsize, textcolor);
                            if (line & 0x40)
                                fillRect(pX + textsize, pY, textsize, textsize,
                                         textcolor);
                            if (line & 0x20)
                                fillRect(pX + 2 * textsize, pY, textsize,
                                         textsize, textcolor);
                            if (line & 0x10)
                                fillRect(pX + 3 * textsize, pY, textsize,
                                         textsize, textcolor);
                            if (line & 0x08)
                                fillRect(pX + 4 * textsize, pY, textsize,
                                         textsize, textcolor);
                            if (line & 0x04)
                                fillRect(pX + 5 * textsize, pY, textsize,
                                         textsize, textcolor);
                            if (line & 0x02)
                                fillRect(pX + 6 * textsize, pY, textsize,
                                         textsize, textcolor);
                            if (line & 0x01)
                                fillRect(pX + 7 * textsize, pY, textsize,
                                         textsize, textcolor);
                        }
                    }
                }
                pY += textsize;
            }

            inTransaction = false;
            spi_end();
        } else
        // Faster drawing of characters and background using block write
        {
            spi_begin();

            setWindow(x, y, (x + w * 8) - 1, y + height - 1);

            uint8_t mask;
            for (int32_t i = 0; i < height; i++) {
                for (int32_t k = 0; k < w; k++) {
                    line = pgm_read_byte((uint8_t *)flash_address + w * i + k);
                    pX   = x + k * 8;
                    mask = 0x80;
                    while (mask) {
                        if (line & mask) {
                            tft_Write_16(textcolor);
                        } else {
                            tft_Write_16(textbgcolor);
                        }
                        mask = mask >> 1;
                    }
                }
                pY += textsize;
            }

            spi_end();
        }
    }
#ifdef LOAD_RLE
    else
#endif
#endif  // FONT2

#ifdef LOAD_RLE  // 674 bytes of code
    // Font is not 2 and hence is RLE encoded
    {
        spi_begin();
        inTransaction = true;

        w *= height;  // Now w is total number of pixels in the character
        if ((textsize != 1) || (textcolor == textbgcolor)) {
            if (textcolor != textbgcolor)
                fillRect(x, pY, width * textsize, textsize * height,
                         textbgcolor);
            int32_t px = 0, py = pY;  // To hold character block start and end
                                      // column and row values
            int32_t pc = 0;           // Pixel count
            uint8_t np =
                textsize * textsize;  // Number of pixels in a drawn pixel

            uint8_t tnp = 0;             // Temporary copy of np for while loop
            uint8_t ts  = textsize - 1;  // Temporary copy of textsize
            // 16 bit pixel count so maximum font size is equivalent to 180x180
            // pixels in area w is total number of pixels to plot to fill
            // character block
            while (pc < w) {
                line = pgm_read_byte((uint8_t *)flash_address);
                flash_address++;
                if (line & 0x80) {
                    line &= 0x7F;
                    line++;
                    if (ts) {
                        px = x +
                             textsize *
                                 (pc %
                                  width);  // Keep these px and py calculations
                                           // outside the loop as they are slow
                        py = y + textsize * (pc / width);
                    } else {
                        px = x +
                             pc % width;  // Keep these px and py calculations
                                          // outside the loop as they are slow
                        py = y + pc / width;
                    }
                    while (
                        line--) {  // In this case the while(line--) is faster
                        pc++;  // This is faster than putting pc+=line before
                               // while()?
                        setWindow(px, py, px + ts, py + ts);

                        if (ts) {
                            tnp = np;
                            while (tnp--) {
                                tft_Write_16(textcolor);
                            }
                        } else {
                            tft_Write_16(textcolor);
                        }
                        px += textsize;

                        if (px >= (x + width * textsize)) {
                            px = x;
                            py += textsize;
                        }
                    }
                } else {
                    line++;
                    pc += line;
                }
            }
        } else  // Text colour != background && textsize = 1
                // so use faster drawing of characters and background using
                // block write
        {
            setWindow(x, y, x + width - 1, y + height - 1);

#ifdef RPI_WRITE_STROBE
            uint8_t textcolorBin[]   = {(uint8_t)(textcolor >> 8),
                                      (uint8_t)textcolor};
            uint8_t textbgcolorBin[] = {(uint8_t)(textbgcolor >> 8),
                                        (uint8_t)textbgcolor};
#endif

            // Maximum font size is equivalent to 180x180 pixels in area
            while (w > 0) {
                line = pgm_read_byte(
                    (uint8_t *)flash_address++);  // 8 bytes smaller when
                                                  // incrementing here
                if (line & 0x80) {
                    line &= 0x7F;
                    line++;
                    w -= line;
#ifdef RPI_WRITE_STROBE
                    spi.writePattern(&textcolorBin[0], 2, 1);
                    line--;
                    while (line--) {
                        WR_L;
                        WR_H;
                    }
#else
#ifdef ESP32_PARALLEL
                    while (line--) {
                        tft_Write_16(textcolor);
                    }
#else
                    writeBlock(textcolor, line);
#endif
#endif
                } else {
                    line++;
                    w -= line;
#ifdef RPI_WRITE_STROBE
                    spi.writePattern(&textbgcolorBin[0], 2, 1);
                    line--;
                    while (line--) {
                        WR_L;
                        WR_H;
                    }
#else
#ifdef ESP32_PARALLEL
                    while (line--) {
                        tft_Write_16(textbgcolor);
                    }
#else
                    writeBlock(textbgcolor, line);
#endif
#endif
                }
            }
        }
        inTransaction = false;
        spi_end();
    }
    // End of RLE font rendering
#endif
    return width * textsize;  // x +
}

/***************************************************************************************
** Function name:           drawString (with or without user defined font)
** Description :            draw string with padding if it is defined
***************************************************************************************/
// Without font number, uses font set by setTextFont()
int16_t TFT_eSPI::drawString(const String &string, int32_t poX, int32_t poY) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawString(buffer, poX, poY, textfont);
}
// With font number
int16_t TFT_eSPI::drawString(const String &string, int32_t poX, int32_t poY,
                             uint8_t font) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawString(buffer, poX, poY, font);
}

// Without font number, uses font set by setTextFont()
int16_t TFT_eSPI::drawString(const char *string, int32_t poX, int32_t poY) {
    return drawString(string, poX, poY, textfont);
}

// With font number. Note: font number is over-ridden if a smooth font is loaded
int16_t TFT_eSPI::drawString(const char *string, int32_t poX, int32_t poY,
                             uint8_t font) {
    int16_t sumX    = 0;
    uint8_t padding = 1, baseline = 0;
    uint16_t cwidth = textWidth(
        string, font);  // Find the pixel width of the string in the font
    uint16_t cheight = 8 * textsize;

#ifdef LOAD_GFXFF
#ifdef SMOOTH_FONT
    bool freeFont = (font == 1 && gfxFont && !fontLoaded);
#else
    bool freeFont = (font == 1 && gfxFont);
#endif

    if (freeFont) {
        cheight = glyph_ab * textsize;
        poY += cheight;  // Adjust for baseline datum of free fonts
        baseline = cheight;
        padding  = 101;  // Different padding method used for Free Fonts

        // We need to make an adjustment for the bottom of the string (eg 'y'
        // character)
        if ((textdatum == BL_DATUM) || (textdatum == BC_DATUM) ||
            (textdatum == BR_DATUM)) {
            cheight += glyph_bb * textsize;
        }
    }
#endif

    // If it is not font 1 (GLCD or free font) get the baseline and pixel height
    // of the font
#ifdef SMOOTH_FONT
    if (fontLoaded) {
        baseline = gFont.maxAscent;
        cheight  = fontHeight();
    } else
#endif
        if (font != 1) {
        baseline = pgm_read_byte(&fontdata[font].baseline) * textsize;
        cheight  = fontHeight(font);
    }

    if (textdatum || padX) {
        switch (textdatum) {
            case TC_DATUM:
                poX -= cwidth / 2;
                padding += 1;
                break;
            case TR_DATUM:
                poX -= cwidth;
                padding += 2;
                break;
            case ML_DATUM:
                poY -= cheight / 2;
                // padding += 0;
                break;
            case MC_DATUM:
                poX -= cwidth / 2;
                poY -= cheight / 2;
                padding += 1;
                break;
            case MR_DATUM:
                poX -= cwidth;
                poY -= cheight / 2;
                padding += 2;
                break;
            case BL_DATUM:
                poY -= cheight;
                // padding += 0;
                break;
            case BC_DATUM:
                poX -= cwidth / 2;
                poY -= cheight;
                padding += 1;
                break;
            case BR_DATUM:
                poX -= cwidth;
                poY -= cheight;
                padding += 2;
                break;
            case L_BASELINE:
                poY -= baseline;
                // padding += 0;
                break;
            case C_BASELINE:
                poX -= cwidth / 2;
                poY -= baseline;
                padding += 1;
                break;
            case R_BASELINE:
                poX -= cwidth;
                poY -= baseline;
                padding += 2;
                break;
        }
        // Check coordinates are OK, adjust if not
        if (poX < 0) poX = 0;
        if (poX + cwidth > width()) poX = width() - cwidth;
        if (poY < 0) poY = 0;
        if (poY + cheight - baseline > height()) poY = height() - cheight;
    }

    int8_t xo = 0;
#ifdef LOAD_GFXFF
    if (freeFont && (textcolor != textbgcolor)) {
        cheight = (glyph_ab + glyph_bb) * textsize;
        // Get the offset for the first character only to allow for negative
        // offsets
        uint16_t c2  = 0;
        uint16_t len = strlen(string);
        uint16_t n   = 0;

        while (n < len && c2 == 0)
            c2 = decodeUTF8((uint8_t *)string, &n, len - n);

        if ((c2 >= pgm_read_word(&gfxFont->first)) &&
            (c2 <= pgm_read_word(&gfxFont->last))) {
            c2 -= pgm_read_word(&gfxFont->first);
            GFXglyph *glyph =
                &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
            xo = pgm_read_byte(&glyph->xOffset) * textsize;
            // Adjust for negative xOffset
            if (xo > 0)
                xo = 0;
            else
                cwidth -= xo;
            // Add 1 pixel of padding all round
            // cheight +=2;
            // fillRect(poX+xo-1, poY - 1 - glyph_ab * textsize, cwidth+2,
            // cheight, textbgcolor);
            fillRect(poX + xo, poY - glyph_ab * textsize, cwidth, cheight,
                     textbgcolor);
        }
        padding -= 100;
    }
#endif

    uint16_t len = strlen(string);
    uint16_t n   = 0;

#ifdef SMOOTH_FONT
    if (fontLoaded) {
        if (textcolor != textbgcolor)
            fillRect(poX, poY, cwidth, cheight, textbgcolor);
        // drawLine(poX - 5, poY, poX + 5, poY, TFT_GREEN);
        // drawLine(poX, poY - 5, poX, poY + 5, TFT_GREEN);
        // fontFile = SPIFFS.open( _gFontFilename, "r");
        if (!fontFile) return 0;

        setCursor(poX, poY);

        while (n < len) {
            uint16_t uniCode = decodeUTF8((uint8_t *)string, &n, len - n);
            drawGlyph(uniCode);
        }
        sumX += cwidth;
        // fontFile.close();
    } else
#endif
    {
        while (n < len) {
            uint16_t uniCode = decodeUTF8((uint8_t *)string, &n, len - n);
            sumX += drawChar(uniCode, poX + sumX, poY, font);
        }
    }

    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG
    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    // Switch on debugging for the padding areas
    //#define PADDING_DEBUG

#ifndef PADDING_DEBUG
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    if ((padX > cwidth) && (textcolor != textbgcolor)) {
        int16_t padXc = poX + cwidth + xo;
#ifdef LOAD_GFXFF
        if (freeFont) {
            poX += xo;  // Adjust for negative offset start character
            poY -= glyph_ab * textsize;
            sumX += poX;
        }
#endif
        switch (padding) {
            case 1:
                fillRect(padXc, poY, padX - cwidth, cheight, textbgcolor);
                break;
            case 2:
                fillRect(padXc, poY, (padX - cwidth) >> 1, cheight,
                         textbgcolor);
                padXc = (padX - cwidth) >> 1;
                if (padXc > poX) padXc = poX;
                fillRect(poX - padXc, poY, (padX - cwidth) >> 1, cheight,
                         textbgcolor);
                break;
            case 3:
                if (padXc > padX) padXc = padX;
                fillRect(poX + cwidth - padXc, poY, padXc - cwidth, cheight,
                         textbgcolor);
                break;
        }
    }

#else

    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG
    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    // This is debug code to show text (green box) and blanked (white box) areas
    // It shows that the padding areas are being correctly sized and positioned

    if ((padX > sumX) && (textcolor != textbgcolor)) {
        int16_t padXc = poX + sumX;  // Maximum left side padding
#ifdef LOAD_GFXFF
        if ((font == 1) && (gfxFont)) poY -= glyph_ab;
#endif
        drawRect(poX, poY, sumX, cheight, TFT_GREEN);
        switch (padding) {
            case 1:
                drawRect(padXc, poY, padX - sumX, cheight, TFT_WHITE);
                break;
            case 2:
                drawRect(padXc, poY, (padX - sumX) >> 1, cheight, TFT_WHITE);
                padXc = (padX - sumX) >> 1;
                if (padXc > poX) padXc = poX;
                drawRect(poX - padXc, poY, (padX - sumX) >> 1, cheight,
                         TFT_WHITE);
                break;
            case 3:
                if (padXc > padX) padXc = padX;
                drawRect(poX + sumX - padXc, poY, padXc - sumX, cheight,
                         TFT_WHITE);
                break;
        }
    }
#endif
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    return sumX;
}

/***************************************************************************************
** Function name:           drawCentreString (deprecated, use setTextDatum())
** Descriptions:            draw string centred on dX
***************************************************************************************/
int16_t TFT_eSPI::drawCentreString(const String &string, int32_t dX,
                                   int32_t poY, uint8_t font) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawCentreString(buffer, dX, poY, font);
}

int16_t TFT_eSPI::drawCentreString(const char *string, int32_t dX, int32_t poY,
                                   uint8_t font) {
    uint8_t tempdatum = textdatum;
    int32_t sumX      = 0;
    textdatum         = TC_DATUM;
    sumX              = drawString(string, dX, poY, font);
    textdatum         = tempdatum;
    return sumX;
}

/***************************************************************************************
** Function name:           drawRightString (deprecated, use setTextDatum())
** Descriptions:            draw string right justified to dX
***************************************************************************************/
int16_t TFT_eSPI::drawRightString(const String &string, int32_t dX, int32_t poY,
                                  uint8_t font) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawRightString(buffer, dX, poY, font);
}

int16_t TFT_eSPI::drawRightString(const char *string, int32_t dX, int32_t poY,
                                  uint8_t font) {
    uint8_t tempdatum = textdatum;
    int16_t sumX      = 0;
    textdatum         = TR_DATUM;
    sumX              = drawString(string, dX, poY, font);
    textdatum         = tempdatum;
    return sumX;
}

/***************************************************************************************
** Function name:           drawNumber
** Description:             draw a long integer
***************************************************************************************/
int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY) {
    isDigits = true;  // Eliminate jiggle in monospaced fonts
    char str[12];
    ltoa(long_num, str, 10);
    return drawString(str, poX, poY, textfont);
}

int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY,
                             uint8_t font) {
    isDigits = true;  // Eliminate jiggle in monospaced fonts
    char str[12];
    ltoa(long_num, str, 10);
    return drawString(str, poX, poY, font);
}

/***************************************************************************************
** Function name:           drawFloat
** Descriptions:            drawFloat, prints 7 non zero digits maximum
***************************************************************************************/
// Assemble and print a string, this permits alignment relative to a datum
// looks complicated but much more compact and actually faster than using print
// class
int16_t TFT_eSPI::drawFloat(float floatNumber, uint8_t dp, int32_t poX,
                            int32_t poY) {
    return drawFloat(floatNumber, dp, poX, poY, textfont);
}

int16_t TFT_eSPI::drawFloat(float floatNumber, uint8_t dp, int32_t poX,
                            int32_t poY, uint8_t font) {
    isDigits = true;
    char str[14];          // Array to contain decimal string
    uint8_t ptr    = 0;    // Initialise pointer for array
    int8_t digits  = 1;    // Count the digits to avoid array overflow
    float rounding = 0.5;  // Round up down delta

    if (dp > 7) dp = 7;  // Limit the size of decimal portion

    // Adjust the rounding value
    for (uint8_t i = 0; i < dp; ++i) rounding /= 10.0;

    if (floatNumber < -rounding)  // add sign, avoid adding - sign to 0.0!
    {
        str[ptr++] = '-';  // Negative number
        str[ptr]   = 0;    // Put a null in the array as a precaution
        digits = 0;  // Set digits to 0 to compensate so pointer value can be
                     // used later
        floatNumber = -floatNumber;  // Make positive
    }

    floatNumber += rounding;  // Round up or down

    // For error put ... in string and return (all TFT_eSPI library fonts
    // contain . character)
    if (floatNumber >= 2147483647) {
        strcpy(str, "...");
        return drawString(str, poX, poY, font);
    }
    // No chance of overflow from here on

    // Get integer part
    uint32_t temp = (uint32_t)floatNumber;

    // Put integer part into array
    ltoa(temp, str + ptr, 10);

    // Find out where the null is to get the digit count loaded
    while ((uint8_t)str[ptr] != 0) ptr++;  // Move the pointer along
    digits += ptr;                         // Count the digits

    str[ptr++] = '.';  // Add decimal point
    str[ptr]   = '0';  // Add a dummy zero
    str[ptr + 1] =
        0;  // Add a null but don't increment pointer so it can be overwritten

    // Get the decimal portion
    floatNumber = floatNumber - temp;

    // Get decimal digits one by one and put in array
    // Limit digit count so we don't get a false sense of resolution
    uint8_t i = 0;
    while ((i < dp) &&
           (digits <
            9))  // while (i < dp) for no limit but array size must be increased
    {
        i++;
        floatNumber *= 10;   // for the next decimal
        temp = floatNumber;  // get the decimal
        ltoa(temp, str + ptr, 10);
        ptr++;
        digits++;             // Increment pointer and digits count
        floatNumber -= temp;  // Remove that digit
    }

    // Finally we can plot the string and return pixel length
    return drawString(str, poX, poY, font);
}

/***************************************************************************************
** Function name:           setFreeFont
** Descriptions:            Sets the GFX free font to use
***************************************************************************************/

#ifdef LOAD_GFXFF

void TFT_eSPI::setFreeFont(const GFXfont *f) {
    textfont = 1;
    gfxFont  = (GFXfont *)f;

    glyph_ab = 0;
    glyph_bb = 0;
    uint16_t numChars =
        pgm_read_word(&gfxFont->last) - pgm_read_word(&gfxFont->first);

    // Find the biggest above and below baseline offsets
    for (uint8_t c = 0; c < numChars; c++) {
        GFXglyph *glyph1 = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
        int8_t ab        = -pgm_read_byte(&glyph1->yOffset);
        if (ab > glyph_ab) glyph_ab = ab;
        int8_t bb = pgm_read_byte(&glyph1->height) - ab;
        if (bb > glyph_bb) glyph_bb = bb;
    }
}

/***************************************************************************************
** Function name:           setTextFont
** Description:             Set the font for the print stream
***************************************************************************************/
void TFT_eSPI::setTextFont(uint8_t f) {
    textfont = (f > 0) ? f : 1;  // Don't allow font 0
    gfxFont  = NULL;
}

#else

/***************************************************************************************
** Function name:           setFreeFont
** Descriptions:            Sets the GFX free font to use
***************************************************************************************/

// Alternative to setTextFont() so we don't need two different named functions
void TFT_eSPI::setFreeFont(uint8_t font) {
    setTextFont(font);
}

/***************************************************************************************
** Function name:           setTextFont
** Description:             Set the font for the print stream
***************************************************************************************/
void TFT_eSPI::setTextFont(uint8_t f) {
    textfont = (f > 0) ? f : 1;  // Don't allow font 0
}

#endif

/***************************************************************************************
** Function name:           writeBlock
** Description:             Write a block of pixels of the same colour
***************************************************************************************/
// Clear screen test 76.8ms theoretical. 81.5ms TFT_eSPI, 967ms Adafruit_ILI9341
// Performance 26.15Mbps@26.66MHz, 39.04Mbps@40MHz, 75.4Mbps@80MHz SPI clock
// Efficiency:
//       TFT_eSPI       98.06%              97.59%          94.24%
//       Adafruit_GFX   19.62%              14.31%           7.94%
//
#if defined(ESP8266) && !defined(ILI9488_DRIVER)
void writeBlock(uint16_t color, uint32_t repeat) {
    uint16_t color16 = (color >> 8) | (color << 8);
    uint32_t color32 = color16 | color16 << 16;

    SPI1W0 = color32;
    SPI1W1 = color32;
    SPI1W2 = color32;
    SPI1W3 = color32;
    if (repeat > 8) {
        SPI1W4 = color32;
        SPI1W5 = color32;
        SPI1W6 = color32;
        SPI1W7 = color32;
    }
    if (repeat > 16) {
        SPI1W8  = color32;
        SPI1W9  = color32;
        SPI1W10 = color32;
        SPI1W11 = color32;
    }
    if (repeat > 24) {
        SPI1W12 = color32;
        SPI1W13 = color32;
        SPI1W14 = color32;
        SPI1W15 = color32;
    }
    if (repeat > 31) {
        SPI1U1 = (511 << SPILMOSI);
        while (repeat > 31) {
#if defined SPI_FREQUENCY && (SPI_FREQUENCY == 80000000)
            if (SPI1CMD & SPIBUSY)  // added to sync with flag change
#endif
                while (SPI1CMD & SPIBUSY) {
                }
            SPI1CMD |= SPIBUSY;
            repeat -= 32;
        }
        while (SPI1CMD & SPIBUSY) {
        }
    }

    if (repeat) {
        repeat = (repeat << 4) - 1;
        SPI1U1 = (repeat << SPILMOSI);
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }
    }
}

#elif defined(ILI9488_DRIVER)

#ifdef ESP8266
void writeBlock(uint16_t color, uint32_t repeat) {
    // Split out the colours
    uint8_t r = (color & 0xF800) >> 8;
    uint8_t g = (color & 0x07E0) >> 3;
    uint8_t b = (color & 0x001F) << 3;
    // Concatenate 4 pixels into three 32 bit blocks
    uint32_t r0 = r << 24 | b << 16 | g << 8 | r;
    uint32_t r1 = g << 24 | r << 16 | b << 8 | g;
    uint32_t r2 = b << 24 | g << 16 | r << 8 | b;

    SPI1W0 = r0;
    SPI1W1 = r1;
    SPI1W2 = r2;

    if (repeat > 4) {
        SPI1W3 = r0;
        SPI1W4 = r1;
        SPI1W5 = r2;
    }
    if (repeat > 8) {
        SPI1W6 = r0;
        SPI1W7 = r1;
        SPI1W8 = r2;
    }
    if (repeat > 12) {
        SPI1W9  = r0;
        SPI1W10 = r1;
        SPI1W11 = r2;
        SPI1W12 = r0;
        SPI1W13 = r1;
        SPI1W14 = r2;
        SPI1W15 = r0;
    }

    if (repeat > 20) {
        SPI1U1 = (503 << SPILMOSI);
        while (repeat > 20) {
            while (SPI1CMD & SPIBUSY) {
            }
            SPI1CMD |= SPIBUSY;
            repeat -= 21;
        }
        while (SPI1CMD & SPIBUSY) {
        }
    }

    if (repeat) {
        repeat = (repeat * 24) - 1;
        SPI1U1 = (repeat << SPILMOSI);
        SPI1CMD |= SPIBUSY;
        while (SPI1CMD & SPIBUSY) {
        }
    }
}
#else  // Now the code for ESP32 and ILI9488

void writeBlock(uint16_t color, uint32_t repeat) {
    // Split out the colours
    uint32_t r = (color & 0xF800) >> 8;
    uint32_t g = (color & 0x07E0) << 5;
    uint32_t b = (color & 0x001F) << 19;
    // Concatenate 4 pixels into three 32 bit blocks
    uint32_t r0 = r << 24 | b | g | r;
    uint32_t r1 = r0 >> 8 | g << 16;
    uint32_t r2 = r1 >> 8 | b << 8;

    if (repeat > 19) {
        SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_PORT), SPI_USR_MOSI_DBITLEN,
                          479, SPI_USR_MOSI_DBITLEN_S);

        while (repeat > 19) {
            while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT)) & SPI_USR)
                ;
            WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), r0);
            WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), r1);
            WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), r2);
            WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), r0);
            WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), r1);
            WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), r2);
            WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), r0);
            WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), r1);
            WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), r2);
            WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), r0);
            WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), r1);
            WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), r2);
            WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), r0);
            WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), r1);
            WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), r2);
            SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
            repeat -= 20;
        }
        while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT)) & SPI_USR)
            ;
    }

    if (repeat) {
        SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_PORT), SPI_USR_MOSI_DBITLEN,
                          (repeat * 24) - 1, SPI_USR_MOSI_DBITLEN_S);
        WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), r2);
        WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), r0);
        WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), r1);
        WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), r2);
        if (repeat > 8) {
            WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), r0);
            WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), r1);
            WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), r2);
            WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), r0);
            WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), r1);
            WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), r2);
            WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), r0);
            WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), r1);
            WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), r2);
        }

        SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
        while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT)) & SPI_USR)
            ;
    }
}
#endif

#else  // Low level register based ESP32 code for 16 bit colour SPI TFTs

void writeBlock(uint16_t color, uint32_t repeat) {
    uint32_t color32 = COL_32(color, color);

    if (repeat > 31)  // Revert legacy toggle buffer change
    {
        WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 511);
        while (repeat > 31) {
            while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT)) & SPI_USR)
                ;
            WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), color32);
            WRITE_PERI_REG(SPI_W15_REG(SPI_PORT), color32);
            SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
            repeat -= 32;
        }
        while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT)) & SPI_USR)
            ;
    }

    if (repeat) {
        // Revert toggle buffer change
        WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), (repeat << 4) - 1);
        for (uint32_t i = 0; i <= (repeat >> 1); i++)
            WRITE_PERI_REG((SPI_W0_REG(SPI_PORT) + (i << 2)), color32);
        SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
        while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT)) & SPI_USR)
            ;
    }
}
#endif

/***************************************************************************************
** Function name:           getSetup
** Description:             Get the setup details for diagnostic and sketch
*access
***************************************************************************************/
void TFT_eSPI::getSetup(setup_t &tft_settings) {
    // tft_settings.version is set in header file

#if defined(ESP8266)
    tft_settings.esp = 8266;
#elif defined(ESP32)
    tft_settings.esp          = 32;
#else
    tft_settings.esp = -1;
#endif

#if defined(SUPPORT_TRANSACTIONS)
    tft_settings.trans = true;
#else
    tft_settings.trans        = false;
#endif

#if defined(ESP32_PARALLEL)
    tft_settings.serial       = false;
    tft_settings.tft_spi_freq = 0;
#else
    tft_settings.serial       = true;
    tft_settings.tft_spi_freq = SPI_FREQUENCY / 100000;
#ifdef SPI_READ_FREQUENCY
    tft_settings.tft_rd_freq  = SPI_READ_FREQUENCY / 100000;
#endif
#endif

#if defined(TFT_SPI_OVERLAP)
    tft_settings.overlap = true;
#else
    tft_settings.overlap      = false;
#endif

    tft_settings.tft_driver = TFT_DRIVER;
    tft_settings.tft_width  = _init_width;
    tft_settings.tft_height = _init_height;

#ifdef CGRAM_OFFSET
    tft_settings.r0_x_offset = colstart;
    tft_settings.r0_y_offset = rowstart;
    tft_settings.r1_x_offset = 0;
    tft_settings.r1_y_offset = 0;
    tft_settings.r2_x_offset = 0;
    tft_settings.r2_y_offset = 0;
    tft_settings.r3_x_offset = 0;
    tft_settings.r3_y_offset = 0;
#else
    tft_settings.r0_x_offset  = 0;
    tft_settings.r0_y_offset  = 0;
    tft_settings.r1_x_offset  = 0;
    tft_settings.r1_y_offset  = 0;
    tft_settings.r2_x_offset  = 0;
    tft_settings.r2_y_offset  = 0;
    tft_settings.r3_x_offset  = 0;
    tft_settings.r3_y_offset  = 0;
#endif

#if defined(TFT_MOSI)
    tft_settings.pin_tft_mosi = TFT_MOSI;
#else
    tft_settings.pin_tft_mosi = -1;
#endif

#if defined(TFT_MISO)
    tft_settings.pin_tft_miso = TFT_MISO;
#else
    tft_settings.pin_tft_miso = -1;
#endif

#if defined(TFT_SCLK)
    tft_settings.pin_tft_clk = TFT_SCLK;
#else
    tft_settings.pin_tft_clk  = -1;
#endif

#if defined(TFT_CS)
    tft_settings.pin_tft_cs = TFT_CS;
#else
    tft_settings.pin_tft_cs   = -1;
#endif

#if defined(TFT_DC)
    tft_settings.pin_tft_dc = TFT_DC;
#else
    tft_settings.pin_tft_dc   = -1;
#endif

#if defined(TFT_RD)
    tft_settings.pin_tft_rd = TFT_RD;
#else
    tft_settings.pin_tft_rd   = -1;
#endif

#if defined(TFT_WR)
    tft_settings.pin_tft_wr = TFT_WR;
#else
    tft_settings.pin_tft_wr   = -1;
#endif

#if defined(TFT_RST)
    tft_settings.pin_tft_rst = TFT_RST;
#else
    tft_settings.pin_tft_rst  = -1;
#endif

#if defined(ESP32_PARALLEL)
    tft_settings.pin_tft_d0 = TFT_D0;
    tft_settings.pin_tft_d1 = TFT_D1;
    tft_settings.pin_tft_d2 = TFT_D2;
    tft_settings.pin_tft_d3 = TFT_D3;
    tft_settings.pin_tft_d4 = TFT_D4;
    tft_settings.pin_tft_d5 = TFT_D5;
    tft_settings.pin_tft_d6 = TFT_D6;
    tft_settings.pin_tft_d7 = TFT_D7;
#else
    tft_settings.pin_tft_d0   = -1;
    tft_settings.pin_tft_d1   = -1;
    tft_settings.pin_tft_d2   = -1;
    tft_settings.pin_tft_d3   = -1;
    tft_settings.pin_tft_d4   = -1;
    tft_settings.pin_tft_d5   = -1;
    tft_settings.pin_tft_d6   = -1;
    tft_settings.pin_tft_d7   = -1;
#endif

#if defined(TOUCH_CS)
    tft_settings.pin_tch_cs   = TOUCH_CS;
    tft_settings.tch_spi_freq = SPI_TOUCH_FREQUENCY / 100000;
#else
    tft_settings.pin_tch_cs   = -1;
    tft_settings.tch_spi_freq = 0;
#endif
}

////////////////////////////////////////////////////////////////////////////////////////
#ifdef TOUCH_CS
#include "Extensions/Touch.cpp"
#include "Extensions/Button.cpp"
#endif

//#include "Extensions/Sprite.cpp"

#ifdef SMOOTH_FONT
#include "Extensions/Smooth_font.cpp"
#endif

////////////////////////////////////////////////////////////////////////////////////////
